不能直接用 heapq 维护单个堆求中位数,因为 heapq 仅支持最小堆、不支持随机访问和任意删除,无法高效定位中间值;需用双堆(小顶堆存大半、负值模拟大顶堆存小半)动态平衡维护中位数。

为什么不能直接用 heapq 维护一个堆求中位数?
因为中位数依赖有序序列的中间位置,而单个最小堆或最大堆无法高效支持「取中间值」和「动态插入后快速重定位」。Python 的 heapq 只提供最小堆,没有内置最大堆,也不能随机访问或删除任意元素——这意味着你不能把所有数塞进一个堆然后“查下标”。
常见错误是试图用 heapq.nsmallest(len(nums)//2 + 1, nums),但这每次都要 O(n log k),且要求数据已全部加载,不适用于「流式输入」场景。
- 流数据意味着数据逐个到达,不能回看或排序全部历史
-
heapq本身不支持堆间元素迁移或平衡操作,必须靠代码逻辑维护两个堆的大小关系 - Python 没有
heappop_max,模拟最大堆得对数值取负再压入最小堆
用两个堆实现中位数:小顶堆存大半、大顶堆存小半
核心思路是维护两个堆:min_heap(存较大的一半,用 heapq 实现的小顶堆)和 max_heap(存较小的一半,用 [-x for x in ...] 模拟的大顶堆)。中位数就落在两个堆顶之间。
关键约束:两个堆大小差不超过 1,且 max_heap[0] ≤ min_heap[0]。每次新数到来,先粗略放入某一边,再通过「弹出-压入」调整平衡。
import heapq
<p>class MedianFinder:
def <strong>init</strong>(self):
self.max_heap = [] # 存较小的一半,用负值模拟大顶堆
self.min_heap = [] # 存较大的一半,标准小顶堆</p><pre class="brush:python;toolbar:false;">def addNum(self, num: int) -> None:
# 先统一压入 max_heap(作为临时缓冲)
heapq.heappush(self.max_heap, -num)
# 把 max_heap 里最大的(即 -min(max_heap))移到 min_heap
heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))
# 如果 min_heap 太大,移一个最小的回 max_heap 保持平衡
if len(self.min_heap) > len(self.max_heap):
heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))
def findMedian(self) -> float:
if len(self.max_heap) == len(self.min_heap):
return (-self.max_heap[0] + self.min_heap[0]) / 2.0
else:
return -self.max_heap[0]
注意:每次 addNum 最多触发两次堆操作,时间复杂度稳定在 O(log n);findMedian 是 O(1)。
容易踩的坑:负号、堆空检查、整除与浮点精度
实际写的时候,这三个地方最容易出错:
- 忘记在
max_heap中对数值取负,或者取负位置不对(比如heapq.heappush(max_heap, -num)写成heapq.heappush(max_heap, num)) - 没做空堆保护,比如直接访问
self.max_heap[0]前未判断是否为空——流刚开始时可能只有一个堆有数据 - 中位数计算时用
//整除导致结果截断,必须显式用/ 2.0或float()转换,否则 Python 3 下整数除仍返回 int(如3/2 == 1)
更安全的写法是:return (-self.max_heap[0] + self.min_heap[0]) / 2(Python 3.6+ 中 / 默认返回 float)。
性能与边界:1 个数、偶数个、重复数都得过
这个双堆结构对边界情况很敏感。测试时至少要覆盖:
- 只加 1 个数:
addNum(5); findMedian() → 5.0 - 加 2 个数:
addNum(1); addNum(2),此时max_heap = [-1],min_heap = [2],中位数是(1+2)/2 = 1.5 - 大量重复值(如全 0):不会破坏堆序,但会频繁在两堆间搬运,逻辑仍正确
- 递增/递减序列:考验堆大小平衡逻辑是否漏掉某次调整
真正难的是调试堆状态——建议在开发时加临时打印:print([−x for x in sorted(self.max_heap)], sorted(self.min_heap)),但上线前务必删掉,因为 sorted(heap) 不等于堆序,仅作观察用。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











