中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
进阶:
如果数据流中所有整数都在 0 到 100 范围内,你将如何优化你的算法?
如果数据流中 99% 的整数都在 0 到 100 范围内,你将如何优化你的算法?
解题报告:
对顶堆的完美应用。
注意点1:在对顶堆启动阶段的时候,只需要判断其中一个堆是否为空就好了,不需要两个都有值。
注意点2:把push元素和堆大小的维护给拆成两个原子操作,便于理解,也便于每一部分代码做唯一的事情。
AC代码:
class MedianFinder {
public:
priority_queue<int> q1;//大根堆
priority_queue<int, vector<int>, greater<int>> q2;//小根堆
MedianFinder() {
}
void addNum(int num) {
if(q1.empty()) q1.push(num);
// else if(q2.empty() && num > q1.top()) q2.push(num);
else if(num < q1.top()) q1.push(num);
else q2.push(num);
if((int)q1.size() - (int)q2.size() > 1) {
int x = q1.top();q1.pop();
q2.push(x);
} else if((int)q2.size() - (int)q1.size() > 1) {
int x = q2.top();q2.pop();
q1.push(x);
}
}
double findMedian() {
if(q1.size()+q2.size() == 0) return 0;
if(q1.size() == 0) return q2.top();
if(q2.size() == 0) return q1.top();
if(q1.size() < q2.size()) return q2.top();
else if(q1.size() > q2.size()) return q1.top();
else return (q1.top()+q2.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();
*/
注意点3:可以人为的做一些约束,限制两个pq的灵活性,来缩短代码量。比如强制要求小根堆的元素多于或等于大跟堆的。
0<=q2.size-q1.size()<=1。
这样实现只需要:
double findMedian() {
if (queMin.size() > queMax.size()) {
return queMin.top();
}
return (queMin.top() + queMax.top()) / 2.0;
}