用小顶堆求top k最大数最省内存,因堆顶为当前最小值,仅当新数更大时才替换,确保最终留下的必为全局最大k个;流式数据须用heapreplace并严守堆长。

直接用 heapq 维护一个大小为 K 的小顶堆,是处理海量数据 Top K 最稳、最省内存的方案——前提是你要清楚什么时候该 push、什么时候该 pushpop、为什么不能用 sorted 或全量建堆。
为什么必须用小顶堆求最大 K 个数
小顶堆的堆顶永远是当前堆里最小的那个值。当你想留“最大的 K 个”,就要保证:任何比堆顶还小的数都不配进堆;只有比堆顶大的新数,才有资格挤掉它。这样一遍扫完全部数据,堆里自然剩下全局最大的 K 个。
- 求最小 K 个数?换成大顶堆(Python 里用负数模拟)
- 堆大小始终控制在 K,空间复杂度严格为
O(K),不是O(N) - 如果误用大顶堆求最大 K 个,堆顶会是最大值,你无法判断后续数是否该保留——逻辑就崩了
heapq.heappushpop() 和 heapq.heapreplace() 别混用
这两个函数看着像,但触发条件和行为完全不同,选错会导致结果错误或漏数:
-
heappushpop(heap, item):先 push 再 pop,**即使item ≤ heap[0]也会 push 进去再立刻 pop 出最小值** → 堆长度不变,但可能把本不该留下的小值临时塞进去又踢出,多一次无效操作 -
heapreplace(heap, item):先 pop 再 push,**要求堆非空,且只在item > heap[0]时才有意义** → 更精准,适合“维持固定大小堆”的场景 - 正确模式是:
if item > heap[0]: heapq.heapreplace(heap, item),而不是无脑heappushpop
初始化阶段不能跳过前 K 个元素的建堆
很多人直接从索引 0 开始遍历,对每个元素都做比较,但前 K 个必须先完整入堆并完成堆化,否则 heap[0] 没意义:
- 错误写法:
for x in nums: if len(heap) —— 这只是插入,没调用 <code>heapify,堆结构未建立,heap[0]不一定是最小值 - 正确做法:用前 K 个元素初始化堆,
heapq.heapify(heap)或逐个heappush(后者自动维护堆序) - 更简洁写法:
heap = nums[:k]; heapq.heapify(heap),然后从nums[k:]开始遍历
结果顺序与去重问题常被忽略
小顶堆最终给出的 K 个数是正确的,但它们在堆数组里**不保证有序**,也不自动去重:
- 要升序输出?用
sorted(heap);要降序(即从大到小)?用sorted(heap, reverse=True) - 原始数据有重复,而你想要“出现频率最高的 K 个元素”?那得先用
collections.Counter统计频次,再按(freq, num)元组入堆 —— 注意元组比较规则:(3, 100)(3, 99) 是 False,因为 100 > 99,所以高频相同时要加负号或调整字段顺序 - 流式数据(如日志行)无法预知总量?必须用
heapreplace+ 守住堆长,不能依赖len(nums)
真正难的不是写对那十几行代码,而是想清楚:你的 K 是多少、数据能不能全加载、是否允许重复、结果要不要保序、频次统计是否前置——这些决策点一旦错,堆再快也没用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











