Top K 问题
在面试中,"Top K 问题"常常以各种面貌出现。先来看看这几个熟悉的场景:
- 给定 100 个整数,找出最大的 10 个。
- 给定 10 亿个无序整数,找出最大的 10 个。
- 给定 10 亿个不重复的整数,找出最大的 10 个。
- 有 10 个 10GB 大小的文件,里面全是 IP 地址,找出访问频次最高的前 10 个。
这些问题看似换汤不换药,但在不同的数据量级、内存限制和查询频率下,解决思路千差万别。稍有不慎,一次暴力的全量排序就会让系统的内存直接 OOM(Out Of Memory),成为性能瓶颈。
下面总结几种常见的解决思路,遇到问题的时候,把这些基础思路融会贯通并且杂糅组合,即可做到见招拆招。
| 解决思路 | 时间复杂度 | 空间复杂度 | 适用场景与优缺点 |
|---|---|---|---|
| 小顶堆法 | O(N log K) | < O(K) | 最通用。适合处理海量流式数据,内存占用极小。求最大 K 个用小顶堆,求最小 K 个用大顶堆。 |
| 快排 | 平均 < O(N) ,最差 < O(N^2) | < O(log N) | 适合数据能全部加载到内存的情况。速度极快,但找出的 Top K 内部是无序的。 |
| Bitmap (位图) | < O(N + MaxValue) | 取决于数据范围 | 适合数据范围已知且不重复的海量数据。极致节省内存,但不适合数据稀疏的场景。 |
| Hash 分治 + 堆 | < O(N) | 取决于分片大小 | 适合超大海量重复数据(如词频统计)。工业界最常用(MapReduce 思想的单机版)。 |
堆排序法
这里说的是堆排序法,而不是快排或者希尔排序。虽然理论时间复杂度都是 O(nlogn),但是堆排在做 topK 的时候有一个优势,就是可以维护一个仅包含 k 个数字的小顶堆(想清楚,为啥是小顶堆哦),当新加入的数字大于堆顶数字的时候,将堆顶元素剔除,并加入新的数字。

代码设计如下:
import java.util.PriorityQueue;
public class TopKExample {
public static void main(String[] args) {
;
[] arr = {, , , , , , , , , };
PriorityQueue<Integer> pq = <>();
( num : arr) {
pq.offer(num);
(pq.size() > topK) {
pq.poll();
}
}
(!pq.isEmpty()) {
System.out.println(pq.poll());
}
}
}




