题目描述
剑指 Offer 41. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
限制:
最多会对 addNum、findMedia进行 50000 次调用。
思路分析
思路一:
数组无序插入 取中位数时快速排序取中间元素
时间复杂度 插入元素O(1) 取出中位数O(n*logn)
思路二:
数组插入过程和直接插入排序类似,插入后即排好序
取中位数直接取中间元素
时间复杂度 插入元素O(n) 取出中位数O(1) O(1)
注:直接插入可以优化成折半插入,虽然元素移动次数一样都是n,但是比较次数折半更好
思路三:
每次插入数组完全有序是不必的,使用堆可以将插入元素的复杂度降为O(logn)
结构规则如下:
堆 数组分为两部分
(1)A为大顶堆 B为小顶堆
(2)A中元素值 均小于 B中元素值
(3)B比A多0或1个元素
以1,2,3,4,5插入顺序为例如下图说明:
总结:插入元素满足结构体可以查找中位数的形态。
对于向大数结合插入的所有情况如下:一开始 小数A(空集合) 大数B(空集合)
初始状态 | 集合中间状态 | 集合最终状态 |
---|---|---|
插入1 | A(空集合) B(1) | A(空集合) B(1) |
插入2 | A(空集合) B(1,2) 个数差2 B给A一个数 | A(1) B(2) |
插入3 | A(1) B(2,3) 符合规范 | A(1) B(2,3) |
插入4 | A(1) B(2,3,4) 个数差2 B给A一个数 | A(1,2) B(3,4) |
插入0 | A(1,2) B(3,4,0) A堆顶大于B的 B和A交换一下堆顶 | A(0,1) B(2,3,4) |
插入-1 | A(0,1) B(2,3,4,-1) 个数差2且A堆顶大于B的 B给A一个 就可以了 | (-1,0,1) B(2,3,4) |
处理流程
1.直接插入B
2.判断个数是否差2,如果差就B堆顶给A
3.判断堆顶是否A的大于B的,如果A>B堆顶,A,B堆顶交换(但是要注意A集合为空无法访问堆顶的情况)
代码实现
初始版代码
class MedianFinder {
public:
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
B.push(num);
//堆顶大小不规范
//集合元素个数差距为2
if(A.size() == B.size()-2) {
A.push(B.top());
B.pop();
}
if(A.size() != 0 && A.top() > B.top()) { // 如果A.top为空时就不能判断就直接跳过即可
int a_top = A.top();
int b_top = B.top();
A.pop();
B.pop();
B.push(a_top);
A.push(b_top);
}
}
double findMedian() {
if(A.size() == B.size()) {
return (A.top()+B.top())/2;
}else {
return B.top();
}
}
private:
priority_queue, less> A;
priority_queue, greater> B;
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
优化版代码
class MedianFinder {
public:
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
B.push(num);
A.push(B.top());
B.pop();
if(A.size() != B.size()) {
B.push(A.top());
A.pop();
}
}
double findMedian() {
return A.size() == B.size() ? (A.top()+B.top())/2 : B.top();
}
private:
priority_queue, less> A;
priority_queue, greater> B;
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/