
👨💻 关于作者:会编程的土豆
"不是因为看见希望才坚持,而是坚持了才看见希望。"
你好,我是会编程的土豆,一名热爱后端技术的Java学习者。
📚 正在更新中的专栏:
-
《数据结构与算法》😊😊😊
-
《leetcode hot 100》🥰🥰🥰🤩🤩🤩
-
《数据库mysql》
💕作者简介:后端学习者
例题例题

cpp
#include <queue>
#include <functional>
using namespace std;
class MedianFinder {
private:
// 大顶堆:存较小的一半数字(堆顶是这半的最大值)
priority_queue<int> maxHeap;
// 小顶堆:存较大的一半数字(堆顶是这半的最小值)
priority_queue<int, vector<int>, greater<int>> minHeap;
public:
MedianFinder() {
// 构造函数,什么都不用做
}
void addNum(int num) {
// 核心规则:
// 1. 新数先进大顶堆(较小的一半)
// 2. 把大顶堆的最大值移到小顶堆
// 3. 如果小顶堆比大顶堆大,把小顶堆的最小值移回大顶堆
maxHeap.push(num);
minHeap.push(maxHeap.top());
maxHeap.pop();
// 保持平衡:maxHeap 的大小要么等于 minHeap,要么比 minHeap 多 1
if (maxHeap.size() < minHeap.size()) {
maxHeap.push(minHeap.top());
minHeap.pop();
}
}
double findMedian() {
// 根据两个堆的大小关系返回中位数
if (maxHeap.size() > minHeap.size()) {
// 奇数个元素,中位数就是大顶堆的堆顶
return maxHeap.top();
} else {
// 偶数个元素,中位数是两个堆顶的平均值
return (maxHeap.top() + minHeap.top()) / 2.0;
}
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
数据流中位数(MedianFinder)详解:为什么要用两个堆?
这道题很多人第一次看到会有点懵:
数据是不断加入的,每次都要快速返回中位数
如果你用最直接的方法:
-
每次插入后排序
-
再取中位数
复杂度是:O(n log n)(排序),明显不行。
一、核心问题
我们要解决的是:
如何在"动态插入数据"的情况下,快速找到中位数?
二、关键思路:把数据分成两半
我们可以这样想:
-
一半较小的数
-
一半较大的数
中位数就在这两部分的"交界处"。
三、为什么用两个堆?
我们用两个堆来维护:
1. 大顶堆(maxHeap)
-
存较小的一半
-
堆顶是"较小部分的最大值"
2. 小顶堆(minHeap)
-
存较大的一半
-
堆顶是"较大部分的最小值"
这样:
-
如果总数是奇数 → 中位数就是 maxHeap.top()
-
如果是偶数 → 中位数是两个堆顶的平均
四、关键难点:如何维持平衡?
必须保证:
-
maxHeap.size() == minHeap.size()
或
-
maxHeap.size() == minHeap.size() + 1
也就是说:
大顶堆最多比小顶堆多一个元素
五、addNum 的核心逻辑(重点)
来看代码:
cpp
void addNum(int num) {
maxHeap.push(num);
minHeap.push(maxHeap.top());
maxHeap.pop();
if(maxHeap.size() < minHeap.size())
{
maxHeap.push(minHeap.top());
minHeap.pop();
}
}
这段代码其实非常巧妙,可以拆成三步理解:
第一步:先把新元素丢进大顶堆
maxHeap.push(num);
先不管大小,直接放进去。
第二步:把最大值丢到小顶堆
minHeap.push(maxHeap.top());
maxHeap.pop();
这一步的作用是:
保证 minHeap 里全是"较大的数"
第三步:重新平衡两个堆
cpp
if(maxHeap.size() < minHeap.size())
{
maxHeap.push(minHeap.top());
minHeap.pop();
}
如果小顶堆多了,就把最小的那个"较大值"搬回来。
六、为什么这样一定正确?
你可以这样理解整个过程:
每次插入:
-
先进入"小的那一半"(maxHeap)
-
再把最大值送去"大的一半"(minHeap)
-
最后调整数量
这样可以保证:
-
maxHeap 里永远是较小的一半
-
minHeap 里永远是较大的一半
而且顺序始终正确。
七、findMedian 就很简单了
cpp
double findMedian() {
if(maxHeap.size() > minHeap.size())
{
return maxHeap.top();
}
else
{
return (maxHeap.top() + minHeap.top()) / 2.0;
}
}
两种情况:
1. 奇数个
maxHeap 多一个
中位数就是:
maxHeap.top()
2. 偶数个
两个堆一样多
中位数:
(maxHeap.top() + minHeap.top()) / 2
八、时间复杂度
-
插入:O(log n)
-
查询中位数:O(1)
相比排序,效率提升非常明显。
九、常见错误
1. 顺序写反
很多人会写成:
minHeap.push(num);
这样会破坏结构。
2. 没有保持大小关系
必须保证:
maxHeap.size() >= minHeap.size()
3. 忘记平衡
少了这一步就会错:
if(maxHeap.size() < minHeap.size())
十、总结一句话
这道题的本质就是:
用两个堆维护"中位数左右两边",保证数量平衡
如果你再往上提升,这道题还有进阶玩法:
-
支持删除(变成滑动窗口中位数)
-
使用 multiset / 平衡树实现
-
手写堆优化
这些在面试中也非常常见。