1.题目内容
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。
示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
2.解题思路及代码(3个方法)
方法1:
相信大家都能想到这个方法,先将数组进行升序排列,然后返回前k个数即可。
代码:
public int[] smallestK(int[] arr, int k) {
Arrays.sort(arr);
int[] ar=new int[k];
for(int i=0;i<k;i++){
ar[i]=arr[i];
}
return ar;
}
在这个代码中Arrays.sort()使用了归并排序,它的时间复杂度为nlogn。
方法2:
将数组的所有元素添加到最小堆里面,依次出队k次。(出的是堆顶元素,即每次出的都是最小值)
补充:JDK的优先级队列默认是最小堆,堆顶存放了最小值
public int[] smallestK(int[] arr, int k) {
Queue<Integer>queue=new PriorityQueue<>();
for(int i:arr){
queue.offer(i);
}
int[] ar=new int[k];
for(int n=0;n<k;n++){
ar[n]=queue.poll();
}
return ar;
}
建堆的时间复杂度为n,整体的代码时间复杂度为nlogn,那么有没有时间复杂度小于nlogn的方法呢?这就用到了方法3的思路。
方法3:
本题中我们需要的结果只是最小的k个数,所有只需要建立一个只保存k个元素的最大堆。
步骤:
1.当最大堆的元素个数小于k时,直接入堆
2.当最大堆元素的个数大于等于k时,将堆顶元素和入堆元素比较,如果入堆元素比堆顶元素还大,则这个元素一定不是本题需要的元素
3.入堆元素小于堆顶时,交换两个元素,继续使堆为最大堆
4.重复上述步骤
(换句话说,本题需要的是最小的k个数,建立最大堆是为了得到k个数的最大值,如果入堆的值比这个最大值还大,则一定不是本题需要的值)
对于这种的TopK问题,用堆解决时,可以遵循下面两句话:取大用小,取小用大
即:要求最大的k个数,就用最小堆,求最小的k个数,就用最大堆。
代码:
public int[] smallestK(int[] arr, int k) {
Queue<Integer>queue=new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer o1,Integer o2){
return o2-o1;
}
});
for(int j:arr){
queue.offer(j);
if(queue.size()>k){
queue.poll();
}
}
//此时队列中保存了最小的k个数
int ar[]=new int[k];
for(int i=0;i<k;i++){
ar[i]=queue.poll();
}
return ar;
}
补充:方法2中的建堆及建堆的时间复杂度这里就不细说了,详情可参考我的另一篇博客http://t.csdn.cn/tP0my