题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4
示例1
输入
[4,5,1,6,2,7,3,8],4
返回值
[1,2,3,4]
思路: 这道题是典型的TopK问题,最简单的思路就是把输入的 n 个整数排序,排序之后位于最前面的 k 个数就是最小的 k 个数,这种思路的时间复杂度是 O(nlogn)
使用快排思想
对数组[l, r]一次快排partition过程可得到,[l, p), p, [p+1, r)三个区间,[l,p)为小于等于p的值
[p+1,r)为大于等于p的值。
然后再判断p
- 如果[l,p), p,也就是p+1个元素(因为下标从0开始),如果p+1 == k, 找到答案
- 如果p+1 < k, 说明答案在[p+1, r)区间内,
- 3, 如果p+1 > k , 说明答案在[l, p)内
实现代码如下:
public ArrayList<Integer> GetLeastNumbers(int[] arr, int k){
ArrayList<Ingeger> res = new ArrayList<>();
if(arr == null)
return res;
int start = 0,end = arr.length - 1;
while(start < end){
int index = partition(arr,start,end);
if(index +1 == k ){ //找到前k小的数了,跳出循环
break;
}
else if(index + 1 < k){//从右边继续寻找
start = index + 1;
}
else if(index + 1 > k){//从左边继续寻找
end = index - 1;
}
}
for(int i = 0;i < k;i++){
res.add(arr[i]);
}
return res;
}
//快排partition操作,返回的索引
public static int partition(int arr,int start,int end){
int key = arr[end];//设置基准值
while(start < end){
while(start < end && arr[start] <= k)
start++;
arr[end] = arr[start];
while(start < end && arr[end] >= k)
end--;
arr[start] = arr[end];
}
arr[start] = key;
return start;
}
注意:
该种解法会修改输入的数组,因为函数会调整数组中的数字的顺序。不能修改输入的数组,可以考虑用以下方法(最大堆):
我们可以先创建一个大小为 k 的数据容器来存储最小的 k 个数字,接下来每次从输入的 n 个整数中读入一个数。如果容器中已有的数字少于 k 个,则直接从把这次读入的整数放入容器中;如果容器中已有 k 个数字了,也就是容器已满,此时我们不能再插入新的数字而只能替换已有的数字了,找出这已有的 k 个数字中的最大值,然后拿这次插入的整数和最大值进行比较,如果待插入的值比当前已有的最大值小,则用这个数替换当前已有的最大值;如果待插入的值比当前已有的最大值还要大,那么这个数就是最小的 k 个整数之一了,于是我们直接扔掉这个数。
因此,当容器满了之后,我们需要做3件事情:一是在 k 个整数中找到最大值;二是有可能在这个容器中删除最大数;三是有可能要插入一个新的数字。如果用一棵二叉树来实现这种数据容器,那么我们能在 O(logk)时间内实现这三步操作。因此 ,对于 n 个输入数字而言,总的时间效率就是 O(nlogk).
由于每次都需要找到 k 个整数中的最大数字,我们可以选择用最大堆,最大堆中,根节点的值总是大于它的子树中任意节点的值。于是每次可以在 O(1)时间内得到已有的 k 个数字中的最大值,但需要 O(logk)时间完成删除及插入操作。
代码如下:
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> result = new ArrayList<Integer>();
int length = input.length;
if(k > length || k == 0){
return result;
}
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
for (int i = 0; i < length; i++) {
if (maxHeap.size() != k) {
maxHeap.offer(input[i]);
} else if (maxHeap.peek() > input[i]) {
Integer temp = maxHeap.poll();
temp = null;
maxHeap.offer(input[i]);
}
}
for (Integer integer : maxHeap) {
result.add(integer);
}
return result;
}
}
不用java中 PriorityQueue 中的集合,自己实现一个最大堆的代码:
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list=new ArrayList<Integer>();
//检查输入的特殊情况,这道题花了好长时间,就是因为这个边界条件
if(input==null || input.length<=0 || input.length<k){
return list;
}
//构建最大堆
int len = input.length;
for(int i=k/2-1; i>=0; i--){
adjustMaxHeapSort(input,i,k);
}
//从第k个元素开始分别与最大堆的最大值做比较,如果比最大值小,则替换并调整堆。
//最终堆里的就是最小的K个数。
int tmp;
for(int i=k; i<len; i++){
if(input[i]<input[0]){
tmp=input[0];
input[0]=input[i];
input[i]=tmp;
adjustMaxHeapSort(input,0,k);
}
}
for(int j=0; j<k; j++){
list.add(input[j]);
}
return list;
}
public void adjustMaxHeapSort(int[] input, int i, int length){
int temp = input[i];
for(int k = 2*i+1;k < length;k=2*k+1){
if(k+1 < length && input[k+1] > input[k])
k++;
if(input[k] > temp){
input[i] = input[k];
i = k;
}
}
input[i] = temp;
}