剑指 Offer 40. 最小的k个数
输入整数数组 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 个数字,那么我们可以一边排序一边寻找,运用快排和堆排的思想,可以做出(代码见下面两个),第一种使用最简单的把,优先队列
其实优先队列就可以看作一个堆,如果是数字小的优先,就是小顶堆,反之大顶堆,定义一个优先队列,使用 lambda 表达式重写排序规则(默认是小顶堆),然后只需要简单的向队列中无脑添加即可,最后取出前 k 个元素
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>((x,y) -> x - y);
int[] res = new int[k];
for (int num : arr) {
queue.offer(num);
}
for (int i = 0; i < k; i++) {
res[i] = queue.poll();
}
return res;
}
}
第一种方法如果在面试的时候,不建议使用,可以说思路,但是具体的堆排序过程最好还是自己实现
本题只是堆排序的一个小变形,当每次 swap 交换元素的时候,计数器加一,加到 k 的时候,说明已经排序出了 k 个最小的元素,此时直接返回数组即可
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
int j = arr.length - 1;
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = arr[0];
swap(arr, 0, j - i);
adjustHeap(arr, 0, j - i);
}
return res;
}
public void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
public void adjustHeap(int[] arr, int i, int length) {
int temp = arr[i];
for (int k = 2 * i + 1; k < length; k = k * 2 + 1) {
if (k + 1 < length && arr[k + 1] < arr[k]) {
k++;
}
if (arr[k] < temp) {
arr[i] = arr[k];
i = k;
} else {
break;
}
}
arr[i] = temp;
}
}
堆排序的相关已经写完了,千万别忘了 21 世纪最牛的算法,快速排序,我们知道,快速排序一般以第一个数为基准,每次排序都能确定一个数的位置,该数前面的元素比它小,后面的元素比它大,那么此题就可以使用快速排序的变形,当某个数排好序的位置正好处于第 k 个位置时,说明前 k - 1 个元素都比这个元素小,此时也就找到了最小的 k 个数,见代码:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
quickSort(arr, 0, arr.length - 1, k);
return Arrays.copyOf(arr, k);
}
public void quickSort(int[] arr, int start, int end, int k) {
if (start > end) {
return;
}
int left = start, right = end;
int temp = arr[start];
while (left < right) {
while (left < right && arr[right] >= temp) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] <= temp) {
left++;
}
arr[right] = arr[left];
}
arr[left] = temp;
if (left == k) {
return;
} else if (left < k) {
quickSort(arr, left + 1, end, k);
} else {
quickSort(arr, start, left - 1, k);
}
}
}
通过 leetCode 的执行结果,就可以发现,快排是最快的,yyds!