1.使用场景
Top K是很常见的一种问题,是指在N个数的无序序列中找出最大的K个数,而其中的N往往都是海量数据,对于这种问题,
- 最容易想到的办法当然就是先对其进行排序,然后直接取出最大的K的元素就行了,但是这种方法时间效率低而且空间开销大,排序是对所有数都要进行排序,而实际上,这类问题只关心最大的K个数,并不关心序列是否有序,因此,排序实际上是浪费了的很多资源都是没必要的
- 堆排序是通过维护大顶堆或者小顶堆来实现
堆排序法来解决N个数中的TopK的思路是:先随机取出N个数中的K个数,将这N个数构造为小顶堆,那么堆顶的数肯定就是这K个数中最小的数了,然后再将剩下的N-K个数与堆顶进行比较,最后还在堆中的K个数就是TopK
2.最小的K个数
1.描述
给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。
要求:空间复杂度 O(n) ,时间复杂度 O(nlogk)
示例1
输入:
[4,5,1,6,2,7,3,8],4
返回值:
[1,2,3,4]
说明:返回最小的4个数即可,返回[1,3,2,4]也可以
2.解题思路
使用大根堆来维护k个最小元素,首先加入k个元素到优先级队列中,遍历加入后面的元素,需要做判断,如果当前元素比对顶元素还小,那么就将对顶元素抛出,加入当前的元素下沉到合适位置,直到最后一个元素判断完成,堆中的k个元素就是最小的k个元素
3.详细代码
public ArrayList<Integer> GetLeastNumbers_Solution (int[] input, int k) {
// write code here
// 使用大根堆,最后在堆里的元素就是最小k个数
ArrayList<Integer> res = new ArrayList<>();
if (k == 0 || input.length == 0) {
return res;
}
PriorityQueue<Integer> q = new PriorityQueue<>((o1,o2)->o2.compareTo(o1));
for (int i = 0; i < k; i++) {
q.offer(input[i]);
}
// 先将k个元素放入优先级队列
for (int i = k; i < input.length; i++) {
if (input[i] < q.peek()) {
q.poll();
q.offer(input[i]);
}
}
// 遍历判断后面的元素
for (int i = 0; i < k; i++) {
res.add(q.poll());
}
return res;
}
3.寻找第K大
1.描述
有一个整数数组,请你根据快速排序的思路,找出数组中第 k 大的数。
给定一个整数数组 a ,同时给定它的大小n和要找的 k ,请返回第 k 大的数(包括重复的元素,不用去重),保证答案存在。
要求:时间复杂度 O(nlogn)O(nlogn),空间复杂度 O(1)O(1)
示例1
输入:
[1,3,5,2,2],5,3
返回值:
2
2.解题思路
使用小根堆,先加入k个元素到优先级队列中,然后将剩余元素依次加入到优先级队列中,需要判断如果当前元素比堆顶元素大,就将堆顶元素抛出,然后将当前元素加入堆中,遍历完整个数组之后,堆顶元素就是第K大的数
3.详细代码
public int findKth (int[] a, int n, int K) {
// write code here
PriorityQueue<Integer> q = new PriorityQueue<>((o1, o2)->o1.compareTo(o2));
if (K == 0 || n == 0) {
return 0;
}
for (int i = 0; i < K; i++) {
q.offer(a[i]);
}
for (int i = K; i < n; i++) {
if (a[i] > q.peek()) {
q.poll();
q.offer(a[i]);
}
}
return q.peek();
}