给定长度为 n 的数组,求其中最大的 K 个元素。这是面试与工程里都很常见的问题。 最朴素的思路是先排序再取后 K 个,复杂度 O(n log n)。当 K 远小于 n 时,有没有更省的办法?

维护「当前最小的 K 个大数」

想象你手里最多只能拿 K 张牌。从左到右扫描数组,每来一个新数 x:

  • 若堆中元素不足 K 个,直接放入;
  • 若已满且 x 大于堆顶(当前 K 个里最小的那个),弹出堆顶再放入 x;
  • 否则忽略 x。

扫描结束后,堆中恰好是原数组最大的 K 个元素。这里用的是小顶堆: 堆顶永远是「目前入选的 K 个数里最弱的」,方便与新元素比较。

复杂度

每个元素最多一次入堆、一次出堆,堆大小恒为 K,单次操作 O(log K),总时间 O(n log K)。 当 K = O(1) 或 K = O(log n) 时,这比全排序划算得多。

与快速选择(Quickselect)的对比

Floyd 的 quickselect 期望 O(n) 找到第 K 大,但最坏 O(n²)。 工程上若需要流式输入、或需要随时知道「当前 Top-K」的集合,堆更自然; 若只需一次性求第 K 大且能接受随机化,quickselect 往往更快。

Python 示意

import heapq

def top_k(nums: list[int], k: int) -> list[int]:
    heap: list[int] = []
    for x in nums:
        if len(heap) < k:
            heapq.heappush(heap, x)
        elif x > heap[0]:
            heapq.heapreplace(heap, x)
    return heap
标准库 heapq.nlargest(k, nums) 在 k 较小时内部也会走堆路线; k 接近 n 时可能退化为排序,这是实现层面的自适应优化。