输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
这道题应该是比较常见的一个问题了,还有一个镜像问题是最大的K个数。总结之后,大概有如下几种解题思路:
1. 排序法
最容易想到的方法,直接按升序对原数组排序,然后输出前K个数即可。时间复杂度依赖于所使用的排序算法,快排为O(nlogn)。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
sort(arr.rbegin(), arr.rend());
vector<int> ans(arr.begin(), arr.begin() + k);
return ans;
}
};
2. 维护一个最小堆
只需要前K个最小的数,而不需要对全部数组排序,因此自然而然就可以想到建立一个最小堆,并且取K次堆顶的元素,最终就得到了最小的前K个数。建堆的时间复杂度为O(n),插入即取出堆元素后堆化的时间复杂度为O(logn),原则上这个算法的时间复杂度也是O(nlogn)(O(klogn)),但是当k比较小时,时间复杂度会想O(logn)靠近,所以相比于O(nlogn)的排序算法,效率还是会高一些。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> ans(k);
int arrLen = arr.size();
this->buildHeap(arr, arrLen);
for (int i = 0; i < k; i++){
ans[i] = arr[0];
arr[0] = arr[--arrLen];
heapify(arr, arrLen);
}
return ans;
}
void buildHeap(vector<int> &arr, int heapSize)
{
// 采用自底向上堆化
// 建堆
for (int i = 1; i < heapSize; i++){
int j = i;
while((j + 1) / 2 >= 1 && arr[j] < arr[(j + 1) / 2 - 1]){
int temp = arr[j];
arr[j] = arr[(j + 1) / 2 - 1];
arr[(j + 1) / 2 - 1] = temp;
j = (j + 1) / 2 - 1;
}
}
}
void heapify(vector<int> &arr, int heapSize)
{
int index = 0;
while (1){
int minPos = index;
if ((index + 1) * 2 - 1 < heapSize && arr[index] > arr[(index + 1) * 2 - 1]){
minPos = (index + 1) * 2 - 1;
}
if ((index + 1) * 2 < heapSize && arr[minPos] > arr[(index + 1) * 2]){
minPos = (index + 1) * 2;
}
if (minPos == index){
break;
}
int temp = arr[index];
arr[index] = arr[minPos];
arr[minPos] = temp;
index = minPos;
}
}
};
3. 维护一个最大堆
思路与第二种方法类似,只不过是变成了维护一个K个元素的最大堆。将剩余的每个元素都和堆顶的元素进行比较,如果小于堆顶元素,就和堆顶元素替换,这样最终这个堆里的元素就是最小的K个数。这种方法相比于第二种方法,减少了一次堆化操作的消耗,但增大了堆化操作的次数,总的来说时间复杂度还是O(nlogn),只不过当K变化时实际的消耗会有所不同。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if (k == 0) return vector<int> ();
vector<int> ans(arr.begin(), arr.begin() + k);
this->buildHeap(ans, k);
int arrLen = arr.size();
for (int i = k; i < arrLen; i++){
if (arr[i] < ans[0]){
ans[0] = arr[i];
heapify(ans, k);
}
}
return ans;
}
void buildHeap(vector<int> &arr, int heapSize)
{
// 采用自底向上堆化
// 建堆
for (int i = 1; i < heapSize; i++){
int j = i;
while((j + 1) / 2 >= 1 && arr[j] > arr[(j + 1) / 2 - 1]){
int temp = arr[j];
arr[j] = arr[(j + 1) / 2 - 1];
arr[(j + 1) / 2 - 1] = temp;
j = (j + 1) / 2 - 1;
}
}
}
void heapify(vector<int> &arr, int heapSize)
{
int index = 0;
while (1){
int maxPos = index;
if ((index + 1) * 2 - 1 < heapSize && arr[index] < arr[(index + 1) * 2 - 1]){
maxPos = (index + 1) * 2 - 1;
}
if ((index + 1) * 2 < heapSize && arr[maxPos] < arr[(index + 1) * 2]){
maxPos = (index + 1) * 2;
}
if (maxPos == index){
break;
}
int temp = arr[index];
arr[index] = arr[maxPos];
arr[maxPos] = temp;
index = maxPos;
}
}
};
4. 基于快速排序的partition
今天看面经看到这个问题,面试官问除了堆之外有没有可以达到O(n)的算法,问的就是基于快速排序的这个操作。
待补
来源:CSDN
作者:Xiami2019
链接:https://blog.csdn.net/weixin_38742280/article/details/104775718