题目;
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,
则最小的4个数字是1、2、3、4。
---------------------
示例:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
-------------
思考:基于快速排序的数组划分
本题使用排序算法解决最直观,对数组 arr 执行排序,再返回前 k 个元素即可。(使用快速排序)
-------------
快速排序原理:
快速排序算法有两个核心点,分别为 “哨兵划分” 和 “递归” 。
哨兵划分操作: 以数组某个元素(一般选取首元素)为 基准数 ,
将所有小于基准数的元素移动至其左边,大于基准数的元素移动至其右边。
为哨兵划分操作流程。通过一轮 哨兵划分 ,
可将数组排序问题拆分为 两个较短数组的排序问题 (本文称之为左(右)子数组)。
-----
递归: 对 左子数组 和 右子数组 递归执行 哨兵划分,直至子数组长度为 1 时终止递归,
即可完成对整个数组的排序。
-----------------------
如下图所示,为示例数组 [2,4,1,0,3,5] 的快速排序流程。
观察发现,快速排序和 二分法 的原理类似,都是以log 时间复杂度实现搜索区间缩小。
![在这里插入图片描述](https://img-blog.csdnimg.cn/5329584fcae9465aa2e29b90af286a76.png)
题目只要求返回最小的 k 个数,对这 k 个数的顺序并没有要求。
因此,只需要将数组划分为 最小的 kk 个数 和 其他数字 两部分即可,而快速排序的哨兵划分可完成此目标。
根据快速排序原理,如果某次哨兵划分后 基准数正好是第 k+1小的数字 ,
那么此时基准数左边的所有数字便是题目所求的 最小的 k 个数 。
根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 k ,
若 true则直接返回此时数组的前 k 个数字即可。
算法流程:
getLeastNumbers() 函数:
1、若 k大于数组长度,则直接返回整个数组;
2、执行并返回 quick_sort() 即可;
quick_sort() 函数:
注意,此时 quick_sort() 的功能不是排序整个数组,而是搜索并返回最小的 k 个数。
1、哨兵划分:
划分完毕后,基准数为 arr[i] ,左 / 右子数组区间分别为 [l, i - 1], [i + 1, r] ;
2、递归或返回:
若 k < i,代表第 k + 1小的数字在 左子数组 中,则递归左子数组;
若 k > i,代表第 k + 1 小的数字在 右子数组 中,则递归右子数组;
若 k = i ,代表此时 arr[k] 即为第 k + 1小的数字,则直接返回数组前 k 个数字即可;
---------------------
![在这里插入图片描述](https://img-blog.csdnimg.cn/32b170bd68594452b2a18bb88c87cf17.png)
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k >= arr.length) return arr;
return quickSort(arr, k, 0, arr.length - 1);
}
private int[] quickSort(int[] arr, int k, int left, int right) {
int i = left, j = right;
while (i < j) {
while (i < j && arr[j] >= arr[left]) j--;
while (i < j && arr[i] <= arr[left]) i++;
swap(arr, i, j);
}
swap(arr, i, left);
if (i > k) return quickSort(arr, k, left, i - 1);
if (i < k) return quickSort(arr, k, i + 1, right);
return Arrays.copyOf(arr, k);
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
LC