堆
当你需要在一个动态的集合中(元素不断在增加或减少)快速地查找并(或)移除“最值”(最大值或最小值)时,堆就是你的不二之选。
Top-K 问题 / 第 K 小/大
这是堆最基础、最直接的应用。核心思想是:维护一个大小固定为 K 的堆。这个堆里存储了你到目前为止见过的 “Top K” 个元素。
问题模型: - 静态数组:在 N 个元素中找到第 K 大的元素 (LC 215)。 - 数据流:在不断加入的数据流中,始终保持对第 K 大元素的快速访问 (LC 703)。
解题思路 (以“第 K
大”为例):你需要一个最小堆(Min-Heap)。为什么?
最小堆的堆顶 min_pq.top()
永远是堆中最小的元素。如果我们的堆里有 K 个元素,那么堆顶就是这
K 个元素中的第 K 大的那个。 -
初始化:创建一个大小为 K
的最小堆 min_pq。 - 遍历数据:for (int num : nums) -
入堆:min_pq.push(num); - 维护大小:if (min_pq.size() >
k):min_pq.pop(); - (将堆中最小的元素——即“第 K+1 大”的元素——扔掉) -
获取答案:当所有数据都处理完毕后,堆中剩下的 K 个元素就是最大的 K 个,而 min_pq.top()
就是我们想要的第 K 大元素。
对顶堆(动态中位数)
一个极其精妙的技巧, 使用两个堆来将一个动态数据流“从中间劈开”,从而 O(1) 访问中位数。
数据结构配置: - small (最大堆):存储数据流中较小的一半元素。small.top() 是“小半”中的最大值。 - large (最小堆):存储数据流中较大的一半元素。large.top() 是“大半”中的最小值。
核心规则(不变式): - small.size() 永远等于 large.size()(当总数为偶) - 或 small.size() 等于 large.size() + 1(当总数为奇)中位数:如果总数为奇,
中位数就是 small.top()。如果总数为偶,中位数就是 (small.top() + large.top()) / 2。