数据流的中位数

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
输入
["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 次调用 addNumfindMedian

使用两个堆大根堆和小根堆分别存储数据流的左半部分和右半部分。让大根堆的首元素(最大值)小于小根堆的首元素(最小值),并且大根堆中的元素个数要么比小根堆中元素个数多1,要么相等。如果相等,中位数就是两个首元素的平均值,否则,中位数就是大根堆的首元素。

可以使用优先队列来实现上述操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class MedianFinder {

private PriorityQueue<Integer> left;
private PriorityQueue<Integer> right;

public MedianFinder() {
//如果新加入的元素b>a,b的优先级更高,所以为大根堆
left = new PriorityQueue<>((a, b) -> b - a);
//默认是小根堆
right = new PriorityQueue<>();
}
/*
先将元素添加到左边的大根堆,再将大根堆的最大值放入小根堆
使得大根堆的元素个数大于等于小根堆元素个数,所以小根堆元素个数如果大于大根堆元素个数,出队,进入大根堆

*/
public void addNum(int num) {
left.offer(num);
right.offer(left.poll());
if (right.size() > left.size()) {
left.offer(right.poll());
}
}

public double findMedian() {
if (left.size() == right.size()) {
return (left.peek() + right.peek()) / 2.0;
} else {
return left.peek();
}
}
}

/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/