[230601 lc295] 数据流的中位数
一 题目
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]
的中位数是3
。 - 例如
arr = [2,3]
的中位数是(2 + 3) / 2 = 2.5
。
实现 MedianFinder 类:
MedianFinder()
初始化MedianFinder
对象。void addNum(int num)
将数据流中的整数num
添加到数据结构中。double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差10-5
以内的答案将被接受。
示例 1:
输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]
解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
提示:
-105 <= num <= 105
- 在调用
findMedian
之前,数据结构中至少有一个元素 - 最多
5 * 104
次调用addNum
和findMedian
二 整体思路
我们使用大顶堆和小顶堆的数据结构,把所有数据看成一个有序数组,左半部分存放在大顶堆中,右半部分存放在小顶堆中,并维护大顶堆的大小始终与小顶堆的大小相等,或者大顶堆的大小比小顶堆的大小大 1。
在这样的情况下,如果数组大小为奇数,那么大顶堆的堆顶元素就是中位数;如果数组大小为偶数,那么大顶堆的堆顶元素和小顶堆的堆顶元素的均值就是中位数。
三 关键点/重点/难点
这道题的难点在,当向数据结构中加入元素的时候,该怎么加。
- 在最开始时,若 left 为空,意味着一个元素都没有,直接加入到左边
- 如果 left 的大小比 right 的大小大 1,则此时要往 right 中添加元素。要往 right 中添加元素,则 num 与 left 的堆顶元素做比较,若大于 left 堆顶元素,则直接将其加入到 right;否则先将 left 的堆顶元素压入 right,弹出后再往 left 加 num。
- 如果 left 的大小等于 right 的大小,则此时要往 left 中添加元素。要往 left 中添加元素,则将 num 与 right 的堆顶元素做比较,若小于 right 堆顶元素,则直接将其加入到 left;否则先将 right 的堆顶元素压入 left,弹出堆顶元素后再往 right 加 num。
四 代码分析
理解了大顶堆、小顶堆以后代码真的很好写。
class MedianFinder {
private:
class Greater {
public:
bool operator()(int a, int b) {
return a < b;
}
};
class Less {
public:
bool operator()(int a, int b) {
return a > b;
}
};
int size;
//若size为奇数,则取left的top;若size为偶数,则取两个top的均值
priority_queue<int, vector<int>, Greater> left; //大顶堆
priority_queue<int, vector<int>, Less> right; //小顶堆
public:
MedianFinder() {
size = 0;
}
void addNum(int num) {
if(left.empty()) {
left.push(num);
} else if(left.size() > right.size()) { //一定往右边加
if(num > left.top()) {
right.push(num);
} else {
right.push(left.top());
left.pop();
left.push(num);
}
} else { //一定往左边加
if(num < right.top()) {
left.push(num);
} else {
left.push(right.top());
right.pop();
right.push(num);
}
}
++size;
}
double findMedian() {
if(size % 2) {
return left.top();
} else {
return (left.top() + right.top()) / 2.0;
}
}
};