← 返回题目列表

Top K 问题怎么用堆解决?求最大的 K 个为什么用小顶堆?

高频 中等 第 12 / 28 题 更新于 2026/07/28
TopK优先队列

简化版

求「最大的 K 个元素」,用一个大小为 K 的小顶堆:遍历数据,堆没满就加入;满了就拿新元素和**堆顶(这 K 个里最小的)**比,比堆顶大就替换掉堆顶。遍历完,堆里就是最大的 K 个,堆顶是「第 K 大」。时间 O(n log K),只需 O(K) 空间——比全排序 O(n log n) 快,尤其适合海量数据。求最大用小顶堆、求最小用大顶堆(反直觉但正确)。

详细版

求最大的 K 个(用小顶堆,size K)

int[] topK(int[] nums, int k) {
    PriorityQueue<Integer> heap = new PriorityQueue<>(); // 默认小顶堆
    for (int x : nums) {
        if (heap.size() < k) heap.offer(x);
        else if (x > heap.peek()) {   // 比 K 个里最小的还大
            heap.poll();              // 淘汰最小的
            heap.offer(x);
        }
    }
    // heap 里就是最大的 K 个,堆顶 heap.peek() 是第 K 大
    return heap.stream().mapToInt(i->i).toArray();
}
需求用什么堆堆顶是
最大的 K 个 / 第 K 大小顶堆(size K)第 K 大(这 K 个里最小)
最小的 K 个 / 第 K 小大顶堆(size K)第 K 小(这 K 个里最大)

完整版教学

一、为什么求「最大 K 个」反而用小顶堆

这是 Top K 最反直觉、也最爱考的点。要求最大的 K 个,我们维护一个只装 K 个元素的小顶堆,堆顶是这 K 个当中最小的。

  • 遍历新元素时,要判断它「配不配进入 Top K」。
  • 判断标准就是:它比当前 K 个里最小的(堆顶)大吗? 大就说明它够格,把最小的(堆顶)淘汰、它进来;不大就说明它连门槛都够不着,丢弃。
  • 小顶堆的堆顶正好是「门槛值」,O(1) 就能拿到、O(log K) 就能替换。

所以小顶堆的堆顶充当「Top K 的守门员」——只有比门槛高的才放进来,同时踢掉当前最弱的。如果用大顶堆,堆顶是最大值,反而没法高效判断「谁该被淘汰」。求最大用小顶堆、求最小用大顶堆,核心就是「堆顶要放最容易被淘汰的那个」。

二、复杂度:为什么是 O(n log K)

  • 遍历 n 个元素:O(n)。
  • 每个元素最多做一次堆操作(插入或替换):O(log K)。
  • 总计 O(n log K),空间 O(K)

对比「全排序再取前 K」的 O(n log n):当 K 远小于 n 时(比如从 10 亿数据里取前 100),O(n log K) 明显更快,且只需 O(K) 内存——海量数据、内存放不下时,堆法是标准解(数据可以流式进来,堆里始终只留 K 个)。

三、其它解法对比

  • 全排序:O(n log n),简单但慢,且要把所有数据读进内存。
  • 堆(size K):O(n log K)、O(K) 空间,适合海量/流式数据。最常用
  • 快速选择(QuickSelect):基于快排 partition,平均 O(n) 找到第 K 大并把前 K 个划到一侧。比堆更快,但要求数据全在内存、会修改数组、最坏 O(n²),且不适合流式数据。

选择:数据量大/流式/内存受限 → 堆数据全在内存、只要一次性求第 K 大 → 快速选择(平均 O(n))

四、常见变体

  • 第 K 大元素:就是「最大 K 个」小顶堆的堆顶
  • 前 K 个高频元素:先用哈希表统计频率,再用「按频率的小顶堆」取 Top K(频率当比较键)。
  • K 个最接近的点 / 值:用大顶堆按「距离」维护 size K,堆顶是当前 K 个里最远的,来了更近的就替换。

框架都一样:维护一个 size K 的堆,堆顶放”最该被淘汰的那个”,新元素和堆顶 PK

五、易错点

  • 堆的方向别搞反:求最大用小顶堆,求最小用大顶堆。
  • 先判断再替换:满了之后,先和堆顶比较,够格才 poll + offer,不要无脑加。
  • 堆大小恒为 K:始终维持 K 个,超了就淘汰堆顶。

六、常见误区与追问

考点正确口径
求最大 K 个维护大小为 K 的小顶堆
求最小 K 个维护大小为 K 的大顶堆
复杂度O(n log K),空间 O(K)
for x in nums:
  if heap.size < k: offer(x)
  else if x > heap.peek:
    poll()
    offer(x)

Top K 反着用堆:求最大 K 个,用小顶堆把候选集合里最小的踢出去。

  • 误区:求最大 K 个必须用大顶堆。 大顶堆适合不断弹出全局最大;维护 K 个最大候选时,小顶堆堆顶是淘汰线。
  • 误区:堆大小可以增长到 n。 Top K 的优势就是堆只保留 K 个元素,空间 O(K)。
  • 误区:Top K 一定要输出有序结果。 堆法得到的是 K 个元素集合,若要求排序,还需要对堆内元素再排序。
  • 追问:K 很接近 n 时还适合堆吗? 此时 O(n log K) 接近排序成本,可直接排序或用快速选择后处理。
  • 追问:快速选择和堆怎么选? 快速选择平均 O(n) 但最坏和实现细节更敏感;堆法稳定、适合流式数据。
  • 追问:数据流 Top K 怎么做? 持续维护一个大小为 K 的小顶堆,每来一个新数按淘汰线更新。

七、加强记忆

Top K 用大小为 K 的堆求最大 K 个用小顶堆(堆顶=这 K 个里最小=门槛,新元素比它大就替换掉它)、求最小 K 个用大顶堆——口诀「堆顶放最该被淘汰的」。时间 O(n log K)、空间 O(K),适合海量/流式数据;第 K 大就是小顶堆堆顶。数据全在内存只求一次可用快速选择(平均 O(n))