一、题目描述
JZ29 最小的K个数
题目大意:给定一个数组, 找出其中最小的k个数
注意审题:若k大于数组的长度, 就返回空数组
二、算法1(排序)
解题思路
- 说到求最小(或最大)的k个数这一类问题, 我们一般是想法就是先对数组进行排序,然后取边缘连续的k个数即可
- 要求最小的k个数, 我们可以将数组从小到大排序, 然后取前k个数为答案即可
代码实现 (C++11)
class Solution { public: vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { vector<int> ans; int n = input.size(); if(k > n) return ans; // 若k大于数组长度, 返回空数组 sort(input.begin(), input.end()); // 从小到大排序 ans = vector<int>(input.begin(), input.begin() + k); // 返回前k个数 return ans; } };
时间复杂度:O(nlogn), 取决于排序的快慢
空间复杂度:O(1)
三、算法2(堆)
解题思路
- 堆(优先队列)这一数据结构可以支持O(logn)的插入和堆顶出队操作, 因此我们可以用一个大小为k的大根堆来维护数组最小的k个数
- 算法实现:当堆的大小小于k时, 直接插入即可; 当堆的大小等于k时, 将当前待插入元素与堆顶元素进行比较, 如果小于堆顶元素, 则堆顶出堆, 当前元素入堆, 否则不插入当前元素, 最后堆中就保留了最小的k个数, 我们再将其取出即可
代码实现(C++11)
class Solution { public: vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { vector<int> ans; int n = input.size(); if(k > n) return ans; priority_queue<int> max_heap; // 创建最大堆 for(int i = 0; i < input.size(); i++){ if(max_heap.size() < k) max_heap.push(input[i]); // 堆的大小不足k else{ if(max_heap.size() && input[i] < max_heap.top()){ // 堆若不空, 则把当前元素与堆顶比较 max_heap.pop(); max_heap.push(input[i]); } } } while(max_heap.size()){ // 取出堆中的k个元素 ans.push_back(max_heap.top()); max_heap.pop(); } return ans; } };
时间复杂度:O(nlogk), 堆的每次操作时间复杂度不超过O(logk)
空间复杂度:O(k)
四、算法三(快速选择算法)
解题思路
- 根据题目可知,返回的数组不必是有序的,故我们可以想到快速选择算法来解决本问题,不熟悉快速选择算法的可以先看看第k个数这题,与其思路一致,我们可以利用快速排序的思想来实现
- 快速排序每次根据一个pivot点对区间进行划分,使得pivot左侧的数都比它小,右侧的数都比它大,然后再对两部分就行划分,而快速选择只需要选择其中一部分进行划分即可,根据k可以选择进入那个递归入口,这样的期望时间复杂度是线性的,但最坏情况下是O(n^2)的
- 算法步骤,首先选择数组中点为pivot,再使用双指针算法交换左侧大于等于pivot的数和右边小于等于pivot的数,最后我们可以保证数组被分为了以pivot划分的两部分,接着根据k选择递归入口即可,算法结束后我们可以保证数组的前k个数是最小的k个数,但是并不是升序的
代码实现(C++11)
class Solution { public: vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { quickSelect(input, 0, input.size() - 1, k); return vector<int>(input.begin(), input.begin() + k); } void quickSelect(vector<int>& input, int l, int r, int k){ if(l >= r) return; int i = l - 1, j = r + 1, pivot = input[l + r >> 1]; // 令小于pivot的数都在左侧, 大于pivot的数都在右侧 while(i < j){ while(input[++i] < pivot); while(input[--j] > pivot); if(i < j) swap(input[i], input[j]); } int cnt = j - l + 1; // cnt表示小于等于pivot的数的数量 if(cnt >= k){ // 若cnt >= k, 说明答案在左区间 return quickSelect(input, l, j, k); } return quickSelect(input, j + 1, r, k - cnt); // 左边cnt个数已经确定, 只要确定右边的即可 } };
时间复杂度:期望O(n),n为数组长度,最坏时间复杂度为O(n^2)
空间复杂度:期望O(logn),递归调用的期望深度为logn,但最坏情况为O(n)