剑指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();
    }
}
posted @ 2020-07-15 10:36  Howfar's  阅读(116)  评论(0编辑  收藏  举报