数据流中的中位数——两个优先队列解决

1,389 阅读3分钟

题目:

得到一个数据流中的中位数。若为奇数,那么中位数就是所有数排序之后位于中间的值;若为偶数,那么中位数就是所有数值排序之后中间两个数的平均数。 [2,3,4] 返回3;[2,3]返回2.5 要求实现两个函数:

  1. void addNum(int num) - 从数据流中添加一个整数到数据结构
  2. double findMedian() - 返回目前所有元素的中位数

思路:

  1. 先用java集合PriorityQueue设置一个大顶堆,一个小顶堆(默认是小顶堆,也就是数字从小到大排序,小的先出队列)
  2. 大顶堆用来存较小的数,按从大到小的顺序排列;小顶堆存较大的数,按从小到大的顺序排列
  3. 这样,小顶堆里的所有数均大于等于大顶堆中的数。这样中位数就是大顶堆队首和小顶堆队首的平均值了。
  4. 每次进队列的时候,不是直接进,而是先进队列,然后poll出一个数,进另一个队列
  5. count用于记录当前两个队列里数字个数之和
  6. 当count为偶数时,说明插入这个数字之后一共有奇数个数字,此时的中位数就是一个数。我们先让它进大顶堆,然后将大顶堆中的最大值(大顶堆中的最大值)弹出插入小顶堆中。最后的中位数就是小顶堆的队首元素。
  7. 当count为奇数时,说明插入这个数字之后是偶数个数字,此时的中位数就是两个数之和。原本为奇数,其实是小顶堆里的数字个数多一个,所以这个新插入的数字需要进大顶堆。所以我们先把数字插入小顶堆中,再将小顶堆中的根节点(小顶堆中的最小值)插入到大顶堆中。

举例:

传入数据为:[5,2,3,4,1,6,7,0,8]应该输出为[5 3.5 3 3.5 3 3.5 4 3.5 4] min表示小顶堆,max表示大顶堆

  • count=0;5进大顶堆,大顶堆中弹出最大值进小顶堆,结果为小顶堆的队首元素 min=[5] max=[] res=[5.00]
  • count=1;2进小顶堆,小顶堆弹出最小值进大顶堆,结果为两个顶堆队首元素的平均值 min=[5] max=[2] res=[3.50]
  • count=2;3进大顶堆,大顶堆中弹出最大值进小顶堆,结果为小顶堆的队首元素 min=[3,5] max=[2], res=[3.00]
  • 以此类推

注意点

  1. 需要重写优先队列的迭代器,默认是小顶堆,大顶堆需要重写:
PriorityQueue<Integer> max = new PriorityQueue<>(15, new Comparator<Integer>(){
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
});
  1. Double类型的使用:

new Double((min.peek() + max.peek()))/2和 new Double((min.peek() + max.peek())/2)是两个不同的结果,我们需要的是第一种

代码

class MedianFinder {
    PriorityQueue<Integer> min;
    PriorityQueue<Integer> max;
    int count = 0;
    /** initialize your data structure here. */
    public MedianFinder() {
        min = new PriorityQueue<>();
        max = new PriorityQueue<>(15, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        }); 
    }
    
    public void addNum(int num) {
        if (count % 2 == 0) {
            max.offer(num);
            min.offer(max.poll());
        } else {
            min.offer(num);
            max.offer(min.poll());
        }
        count++;
    }
    
    public double findMedian() {
        if (count % 2 == 1) {
            return new Double(min.peek());
        } else {
            return new Double((min.peek() + max.peek())) / 2;
        }
    }
}

使用如下:

MedianFinder obj = new MedianFinder();
obj.addNum(num);
double param_2 = obj.findMedian();