大家好!我是曾续缘😙
今天是《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
解题方法
这个题目要求我们实现一个数据流的中位数 finder,即 MedianFinder 类。该类需要维护一个数据流,并能快速地找到目前为止所有元素的中位数。
因为中位数把数据分成两个大小相等的集合,其中一个集合的数都小于等于中位数,另一个集合的数都大于等于中位数。如果数据量为偶数,则是两个数把数据分成两个大小相等的集合,中位数就是两个数的平均。
我们使用两个优先队列来维护数据流,一个用来维护小于等于中位数的集合(命名为 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 的数量加 1if(l.size() > r.size() + 1){// 将 l 的顶部元素转移到 rr.offer(l.poll());}}else{// 如果 num 大于 l 的顶部元素r.offer(num);// 如果 r 的数量大于 l 的数量if(r.size() > l.size()){// 将 r 的顶部元素转移到 ll.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;
}
这样,我们就完成了一个 MedianFinder 类的实现,它可以在数据流中快速找到中位数。
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();*/