一、题目描述
输入整数数组 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个元素即可。快速排序的详细思路可以参照这篇博客:数据结构与算法:快速排序
快速排序的三大步骤:
①选哨兵(主元):为了方便起见,可以选择数组最左边的元素作为哨兵
②子集划分:划分过后,左边的子集都小于哨兵,右边的子集都大于哨兵
③递归:对左右子集继续进行选哨兵、子集划分的操作
子集划分的具体案例如下:
对子集进行递归的具体案例如下:
复杂度分析:
时间复杂度 O ( N l o g N ) \rm{O(NlogN)} O(NlogN):库函数、快排等排序算法的平均时间复杂度为O(NlogN)
空间复杂度 O ( N ) \rm{O(N)} O(N):快速排序的递归深度最好(平均)为O(logN)
,最差情况(即输入数组完全倒序)为O(N)
算法改进
题目只要求返回最小的
k
个数,对这k
个数的顺序并没有要求。因此,只需要将数组划分为最小的k
个数和其他数字两部分即可,而快速排序的哨兵划分可完成此目标。根据快速排序原理,如果某次哨兵划分后 基准数正好是第k+1
小的数字,那么此时基准数左边的所有数字便是题目所求的最小的k
个数。根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于k
,若true
则直接返回此时数组的前k
个数字即可。
算法流程:
getLeastNumbers() 函数
①若k
大于数组长度,则直接返回整个数组。若k小于等于0,返回NULL。
②执行并返回 quick_sort() 函数即可。
quick_sort() 函数
注意,此时 quick_sort() 的功能不是排序整个数组,而是搜索并返回最小的k
个数。
①哨兵划分:
划分完毕后,哨兵(主元)为arr[i]
,左/右子数组区间分别为[l,i−1]
、[i+1,r]
②递归或返回:
若k<i
,代表第k+1
小的数字在左子数组中,则递归左子数组
若k>i
,代表第k+1
小的数字在右子数组中,则递归右子数组
若k=i
,代表此时arr[k]
即为第k+1
小的数字,则直接返回数组前k
个数字即可
案例分析:
复杂度分析:
时间复杂度 O ( N ) \rm{O(N)} O(N):其中N
为数组元素数量,对于长度为N
的数组执行哨兵划分操作的时间复杂度为O(N)
。每轮哨兵划分后根据k
和i
的大小关系选择递归,由于i
分布的随机性,则向下递归子数组的平均长度为 N 2 \frac{N}{{\rm{2}}} 2N。因此平均情况下,哨兵划分操作一共有 N + N 2 + N 4 + . . . + N N = N − 1 2 1 − 1 2 = 2 N − 1 N{\rm{ + }}\frac{N}{{\rm{2}}}{\rm{ + }}\frac{N}{{\rm{4}}} + ... + \frac{N}{N} = \frac{{N - \frac{1}{{\rm{2}}}}}{{1 - \frac{1}{{\rm{2}}}}} = 2N - 1 N+2N+4N+...+NN=1−21N−21=2N−1(等比数列求和),即总体时间复杂度为O(N)
。
空间复杂度 O ( l o g N ) \rm{O(logN)} O(logN):划分函数的平均递归深度为O(logN)
。
三、整体代码
整体代码如下
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int partition(int* arr , int start , int end ){
int privot = arr[start]; //直接让主元等于最左边的元素
while(start < end){
//主元右边的元素≥主元,同时左右边界未互相穿过,则end--
while(start < end && arr[end] >= privot){
end--;
}
if(start < end){
arr[start++] = arr[end];
}
//主元左边的元素≤主元,同时左右边界未互相穿过,则start++
while(start < end && arr[start] <= privot){
start++;
}
if(start < end){
arr[end--] = arr[start];
}
}
arr[start] = privot;
return start;
}
int* quickSort(int* arr , int start , int end , int k){
if(start < end){
int mid = partition(arr, start , end);
if(mid == k ){
return arr;
}
if(mid > k ){
return quickSort(arr, start , mid , k);
}
if(mid < k ){
return quickSort(arr, mid + 1 , end, k);
}
}
return arr;
}
int* getLeastNumbers(int* arr, int arrSize, int k, int* returnSize){
// 数组为空/k≤0/,直接返回NULL
if(arr == NULL || k <= 0){
*returnSize = 0;
return NULL;
}
if(k >= arrSize && arrSize > 0){
*returnSize = k;
return arr;
}
*returnSize = k;
//进行快速排序,左边起始位置下标为0,右边结束位置下标为arrSize-1
return quickSort(arr, 0 , arrSize -1 , k );
}
运行,测试通过