原题链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/
原题
输入整数数组 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法
1. 直接sort排序取前k
最直接最暴力的方式,不做过多叙述
2. 小顶堆
建立一个小顶堆,将所有的数据放进去,然后再取出k个数字即可。
在Java中使用java.util.PriorityQueue
类,其默认是一个小顶堆。
代码如下:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int i = 0; i < arr.length; i ++) {
queue.add(arr[i]);
}
int[] res = new int[k];
for (int i = 0; i < k; i ++) {
res[i] = queue.poll();
}
return res;
}
}
3. 大顶堆
使用大顶堆维护一个容量只有k + 1
个的堆,循环遍历数组:
(1) 若当前堆中元素数量小于k,则直接将当前元素添加进入堆
(2) 若当前堆中元素数量等于k个,则将当前元素添加进入堆,并取出堆顶元素
这样保证堆中的所有元素为最小
代码如下:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>(k + 1, new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
};
});
for (int i = 0; i < arr.length; i ++) {
queue.add(arr[i]);
if (queue.size() == k + 1) {
queue.poll();
}
}
int[] res = new int[k];
for (int i = 0; i < k; i ++) {
res[i] = queue.poll();
}
return res;
}
}
4. 类似于快排的思想
快排大致思想
在快速排序的过程中,主要是将带排序数组的第一个数字作为一个临界值,然后通过左右双指针遍历待排序数组,并进行交换,使得所有小于临界值的数字在临界值的左边,所有大于临界值的数字在临界值的右边
所以若临界值所在的下标为index,那么其右边的index个数字一定是该数组中最小的index个数字。
快速排序核心代码(伪代码版本)
public void fastSort(int arr[], int start, int end) {
if (start >= end) return;
int num = arr[start];
int l = start, r = end;
while (l < r) {
while (l < r && arr[r] >= num) {
r --;
}
// 代表交换两个值
swap(arr[l], arr[r]);
while (l < r && arr[l] <= num) {
l ++;
}
swap(arr[l], arr[r]);
}
// 这里的mid就是临界值的下标
int mid = l;
fastSort(arr, start, mid);
fastSort(arr, mid + 1, end);
}
因此在获取到临界值排序后的下标之后,就有以下三种情况
**设临界值左边(包含临界值)的长度length = mid - start + 1
**
length == k
喜大普奔,直接返回即可,左边刚好够length < k
左边的不够,那么需要再从右边拿length - k
个数字才满足最小的k个数字的要求,因此对右边[mid+1, end]这个区间再一次执行排序,只不过此时其对应的k变成了k - length
length > k
左边的的比k个多,那么从中取k个就可以?
这里一定要注意,左边的length个数字并不保证是有序的,其只能保证其一定小于临界值,因此不能直接从左边区域拿前k个数字作为答案
因此最终的代码大概如下:
import java.util.*;
class Main {
static public void main(String[] argv) {
int[] input = {0,1,1,1,4,5,3,7,7,8,10,2,7,8,0,5,2,16,12,1,19,15,5,18,2,2,22,15,8,22,17,6,22,6,22,26,32,8,10,11,2,26,9,12,9,7,28,33,20,7,2,17,44,3,52,27,2,23,19,56,56,58,36,31,1,19,19,6,65,49,27,63,29,1,69,47,56,61,40,43,10,71,60,66,42,44,10,12,83,69,73,2,65,93,92,47,35,39,13,75};
Solution sol = new Solution();
int[] res = sol.getLeastNumbers(input, 75);
for (int i = 0; i < res.length; i ++) {
System.out.printf("%d ", res[i]);
}
System.out.println();
}
}
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (arr.length == 0 || k == 0) return new int[0];
fastSort(arr, 0, arr.length - 1, k);
int[] res = new int[k];
for (int i = 0; i < k; i ++) {
res[i] = arr[i];
}
Arrays.sort(res);
return res;
}
private void fastSort(int[] arr, int l, int r, int k) {
if (l >= arr.length || l > r) return;
int firstNum = arr[l];
int lastR = r;
int lastL = l;
while (l < r) {
while (l < r && arr[r] >= firstNum) {
r --;
}
// System.out.printf("sl: %d sr: %d l: %d r: %d k: %d\n", lastL , lastR, l, r, k);
{
int temp = arr[r];
arr[r] = arr[l];
arr[l] = temp;
}
while (l < r && arr[l] <= firstNum) {
l ++;
}
// System.out.printf("sl: %d sr: %d l: %d r: %d k: %d\n", lastL , lastR, l, r, k);
{
int temp = arr[r];
arr[r] = arr[l];
arr[l] = temp;
}
}
int mid = l;
int length = mid - lastL + 1;
if (k > length) {
fastSort(arr, mid + 1, lastR, k - length);
}
else if (k < length) {
fastSort(arr, lastL, mid, k);
}
}
}
后记
三种解法提交的情况
从上往下依次是大顶堆、小顶堆、类似于快排的思想