题目链接:top-k问题:最小的K个数
top-k问题:最小的K个数假
- 1.方法一
- 2.方法二
- 时间复杂度
- 3.方法三
- 时间复杂度
1.方法一
各种排序算法(由于本文主要讲有关堆的使用,这里不做有关排序算法解决本题的介绍。对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决。
2.方法二
记录数组arr[]的元素个数n,用向上调整法建一个小根堆。由于用向上调整法建立小根堆后,遍历堆中的数据时,下一个元素的值一定>= 上一个元素的值。然后删除小根堆的堆顶元素k次到存储数据的数组tmp[]中,这样tmp[]中记录的就是前k个最小的数。
代码如下:
class Solution {public int[] smallestK(int[] arr, int k) {PriorityQueue<Integer> heap = new PriorityQueue<>(); //n*logn 以向上插入的方法建堆for(int i = 0; i < arr.length;i++){heap.offer(arr[i]);}//k*logn 删除堆顶的K个元素到目标数组中int[] tmp = new int[k];//创建数组tmp[]存储前k个最小数据for(int i = 0;i < k;i++){tmp[i] = heap.poll();}return tmp;}
}
时间复杂度
以向上调整法建堆的过程中时间复杂度为T1(n) = (n+1)*( log2(n+1) - 2) + 2,删除堆顶k个元素的时间复杂度为T2(n) = k*log2( n+1 ),因此总的时间复杂度为:T(n)= (n+k+1) * log2(n+1);
3.方法三
思考:arr[]数组中有n个元素,我们只需要取出前k个最小的元素。在方法二中,我们建立了一个大小为n的小根堆。考虑到我们最终只需要保留k个数据,我们能否建立一个大小为k的堆,在这个堆上对数据进行操控,从而降低程序的时间复杂度呢?答案是肯定的。
图示如下:
代码如下:
//实现comparator接口,为了创立比较器
class Imp implements Comparator<Integer>{public int compare(Integer o1,Integer o2){return o2.compareTo(o1); //注意,创建大根堆,必须o2在前o1在后}
}
class Solution {public int[] smallestK(int[] arr, int k) {int[] tmp = new int[k];if(k == 0){//k==0 单独处理,防止空指针异常return tmp;}Imp imp = new Imp();PriorityQueue<Integer> maxHeap = new PriorityQueue<>(imp);//建立一个大小为3的大根堆for(int i = 0;i < k;i++){maxHeap.offer(arr[i]);} for(int i = k;i < arr.length;i++){//arr[i]比堆顶元素小if(arr[i] < maxHeap.peek()){maxHeap.poll();maxHeap.offer(arr[i]);}}for(int i = 0;i < k;i++){tmp[i] = maxHeap.poll();}return tmp;}
}
时间复杂度
建堆过程:T1(n) = k*log2(k + 1)
for(int i = 0;i < k;i++){maxHeap.offer(arr[i]);}
删除添加元素过程:T2(n) = (n-k) * log2(k + 1);
for(int i = k;i < arr.length;i++){//arr[i]比堆顶元素小if(arr[i] < maxHeap.peek()){maxHeap.poll();maxHeap.offer(arr[i]);}}
所以总的时间复杂度T(n) = n*log2(k+1)-----(1)
对比方法二的时间复杂度T(n)= (n+k+1) * log2(n+1) -----(2)
(1)式的值更小,特别当n是一个极大的数比如100000,而k只是一个就很小的数比如10时,两个式子在对数真数部分的差值巨大,(1)式的值远小于(2)式。
当然方法二也有比较麻烦的地方,在于它需要创建一个大根堆,而Java默认创建的是小根堆,因此需要一个类实现Comparator接口并重写里面的compare()方法。这部分内容我会在其他文章做介绍,这里由于不是本文的核心暂时就忽略掉了。