大家好!我是曾续缘😙
今天是《LeetCode 热题 100》系列
发车第 76 天
堆第 3 题
❤️点赞 👍 收藏 ⭐再看,养成习惯
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
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
解题方法
中位数把数据分成两个大小相等的集合,其中一个集合的数都小于等于中位数,另一个集合的数都大于等于中位数。如果数据量为偶数,则是两个数把数据分成两个大小相等的集合,中位数就是两个数的平均。
我们使用两个优先队列来维护数据流,一个用来维护小于等于中位数的集合(命名为 l
),另一个用来维护大于等于中位数的集合(命名为 r
),我们该使用大根堆还是小根堆?
如果中位数在l
集合中,那它就是l
集合的最大值,因此l
集合使用大根堆。
如果中位数在r
集合中,那它就是r
集合的最小值,因此r
集合使用小根堆。
有了两个堆之后,我们需要时刻保证中位数在堆顶,也就是说l
和r
集合的数量需要尽可能平均。
1. MedianFinder 类的初始化
MedianFinder()
初始化 MedianFinder 对象。这里我们使用两个优先队列,一个用于存储较大的数(命名为 r),另一个用于存储较小的数(命名为 l)。
class MedianFinder {
PriorityQueue<Integer> l; // 用于存储较小的一半
PriorityQueue<Integer> r; // 用于存储较大的一半
public MedianFinder() {
l = new PriorityQueue<Integer>((a, b) -> (b - a));
r = new PriorityQueue<Integer>();
}
// ... 其他方法
}
2. addNum 方法
void addNum(int num)
将数据流中的整数 num 添加到数据结构中。这个方法是这个类的主要方法,确保了数据流的中位数可以被快速找到。
- 如果 l 是空的,或者 num 小于等于 l 的顶部元素,那么 num 会被加入到 l 中。
- 然后,如果 l 的数量大于 r 的数量加 1,那么 l 的顶部元素会被转移到 r 中,以保证 l 和 r 的数量接近。
- 如果 num 大于 l 的顶部元素,那么 num 会被加入到 r 中。
- 然后,如果 r 的数量大于 l 的数量,那么 r 的顶部元素会被转移到 l 中。 这样,我们就可以确保 l 和 r 的数量相差不超过 1,而中位数就是 l 的顶部元素(如果 l 的数量大于 r 的数量)或者 l 和 r 顶部元素的平均值(如果 l 和 r 的数量相等)。
查看代码
public void addNum(int num) {
// 如果 l 是空的,或者 num 小于等于 l 的顶部元素
if(l.isEmpty() || num <= l.peek()){
l.offer(num);
// 如果 l 的数量大于 r 的数量加 1
if(l.size() > r.size() + 1){
// 将 l 的顶部元素转移到 r
r.offer(l.poll());
}
}else{
// 如果 num 大于 l 的顶部元素
r.offer(num);
// 如果 r 的数量大于 l 的数量
if(r.size() > l.size()){
// 将 r 的顶部元素转移到 l
l.offer(r.poll());
}
}
}
3. findMedian 方法
double findMedian()
返回到目前为止所有元素的中位数。这个方法直接利用了 l 和 r 的性质来找到中位数。
- 如果 l 的数量大于 r 的数量,那么中位数就是 l 的顶部元素。
- 如果 l 和 r 的数量相等,那么中位数就是 l 和 r 顶部元素的平均值。
public double findMedian() {
// 如果 l 的数量大于 r 的数量
if(l.size() > r.size()){
// 中位数就是 l 的顶部元素
return l.peek();
}
// 如果 l 和 r 的数量相等
return (l.peek() + r.peek()) / 2.0;
}
Code
查看代码
class MedianFinder {
PriorityQueue<Integer> l;
PriorityQueue<Integer> r;
public MedianFinder() {
l = new PriorityQueue<Integer>((a, b) -> (b - a));
r = new PriorityQueue<Integer>((a, b) -> (a - b));
}
public void addNum(int num) {
if(l.isEmpty() || num <= l.peek()){
l.offer(num);
if(l.size() > r.size() + 1){
r.offer(l.poll());
}
}else{
r.offer(num);
if(r.size() > l.size()){
l.offer(r.poll());
}
}
}
public double findMedian() {
if(l.size() > r.size()){
return l.peek();
}
return (l.peek() + r.peek()) / 2.0;
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/