剑指Offer_#41_ 数据流中的中位数
剑指Offer_#41_ 数据流中的中位数
Contents
题目
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
限制:
最多会对 addNum、findMedia进行 50000 次调用。
思路分析
借助Java中的优先队列PriorityQueue,分别创建大顶堆和小顶堆,大顶堆保存数据流中较小的一半数字,小顶堆保存数据流中较大的一半数字。如下图所示。
将所有数字正确地加入到大顶堆和小顶堆之后,再根据数据流长度的奇偶来计算中位数。
关键在于如何将数字正确的加入大顶堆和小顶堆呢?每一次输入的数字是混乱无序的,如何知道当前输入的数字是属于较小的一半还是较大的一半?
有一个很巧妙的方法,就是不用直接选择当前数字加入到大顶堆还是小顶堆,而是大顶堆和小顶堆互相配合,互相“倒腾”数据。
具体来说,如果我想在大顶堆加入一个数字,并不是直接把当前输入的数字加入,而是先把当前输入的数字加入小顶堆,然后弹出小顶堆当中最小的数字,加入大顶堆。反之同理。
这样的方法可以保证,大顶堆里边的数字比小顶堆里的数字要小。
解答
class MedianFinder {
Queue<Integer> A,B;
/** initialize your data structure here. */
public MedianFinder() {
A = new PriorityQueue<>();//小顶堆,保存较大的一半
B = new PriorityQueue<>((x,y) -> y-x);//大顶堆,保存较小的一半
}
public void addNum(int num) {
if(A.size() == B.size()){
//先加入A,再加入B,如果长度相同,说明上次加入的是B,这次该加入A
//先把num加入B,再把B的顶弹出加入到A(即把B中最大的加入A)
B.add(num);
A.add(B.poll());
}else{
//同理,先把num加入A,再把A的顶弹出加入到B(把A中的最小加入到B)
A.add(num);
B.add(A.poll());
}
}
public double findMedian() {
//整个数据流长度为偶数
if(A.size() == B.size()) return (A.peek() + B.peek()) / 2.0;
//整个数据流长度为奇数,那么中位数就是A的顶
else return A.peek();
}
}