题目要求
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
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
思路解析
实际上如果用一个列表来存,那么存新数据的时候就要用O(n)去调整,find的时候要O(logn)
那么我们已经确认了是找中位数,那么只要用一个大跟堆存小一半元素,小根堆存大一半元素就能很快解决问题了,并且维护小根堆元素数量是大根堆的相同或者大根堆数量+1
代码&注释
java
class MedianFinder {
// 大根堆:存放「较小的一半数据」
// 堆顶是较小部分中最大的那个
PriorityQueue<Integer> queMin;
// 小根堆:存放「较大的一半数据」
// 堆顶是较大部分中最小的那个
PriorityQueue<Integer> queMax;
/**
* 初始化两个堆
*/
public MedianFinder() {
// 大根堆:b - a 表示大的排前面
queMin = new PriorityQueue<>((a, b) -> b - a);
// 小根堆:a - b 表示小的排前面
queMax = new PriorityQueue<>((a, b) -> a - b);
}
/**
* 向数据流中加入一个数
*/
public void addNum(int num) {
// 情况 1:大根堆为空
// 或者 num <= 较小部分最大值
// → 说明 num 应该放进「较小的一半」
if (queMin.isEmpty() || num < queMin.peek()) {
queMin.offer(num);
// 插入后如果破坏了「大小平衡」
// 大根堆最多只能比小根堆多 1 个
if (queMin.size() > queMax.size() + 1) {
// 把大根堆堆顶(最大的小数)移到小根堆
queMax.offer(queMin.poll());
}
} else {
// 情况 2:num 比较大
// → 放进「较大的一半」
queMax.offer(num);
// 如果小根堆元素数量超过大根堆
if (queMax.size() > queMin.size()) {
// 把较大部分最小的数移到大根堆
queMin.offer(queMax.poll());
}
}
}
/**
* 返回当前数据流的中位数
*/
public double findMedian() {
// 奇数个元素
// 大根堆一定多一个
if (queMin.size() > queMax.size()) {
return queMin.peek();
}
// 偶数个元素
// 取两边堆顶平均
return (queMin.peek() + queMax.peek()) / 2.0;
}
}