LeetCode每日一题 之 数据流中的中位数

217 阅读3分钟

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。题目地址

基本思路

题目的要求是动态插入后动态取出,如果插入后每次都对整个数据流进行排序的话,那么效率是非常低的。但是每次插入新的数据,肯定是要排序的,怎么才能提高效率,减小排序带来的性能损耗呢。想想中位数的特点,中间的数字,我们可否分成两个部分排序,保证一半所有数值小于另一半,这样中位数就在两者之间。而且这样插入的数字只需要让一半的数据重新排序即可。

解题方法

java中提供默认排序的集合类是非常多的,我们可以使用PriorityQueue大顶堆和小顶堆解这道题。

小顶堆定义:使用数组存储的树,根节点为最小的值,大顶堆相反,根结点是最大的值

我们使用大顶堆存储小的数,小顶堆存储较大的数。中位数就在两个堆堆顶之一或者是平均数。

需要考虑的问题

  • 如何确保大顶堆里的值都小于小顶堆里的呢? 我们只要从大顶堆取出最大的值放入小顶堆即可,这样就可以保证。
  • 怎么确保两个堆里数字个数均等,相等或只差一呢?
    我们只需要交替的往两个堆插入数字即可,插入小顶堆时,拿出最小的插入大顶堆。插入大顶堆时拿出最大的插入小顶堆。

这样如果是偶数个数中位数就是大顶堆和小顶堆的平均值,奇数个数中位数就是不是首次插入的堆的根结点。

我们举个栗子🌰

输入的数据流是[5,2,3,4,1] max代编大顶堆,min代表小顶堆

  1. 插入5,我们插入min,把min最小的放入max。min[] max[5],奇数个中位数max的堆顶,也就是5
  2. 插入2,我们插入max, 把max最大的放入min,min[5] max[2],偶数个堆顶的平均数
  3. 插入3,我们插入min,把min最小的放入max,min[5],max[2,3]奇数个中位数max的堆顶,也就是3
    。。。。。

代码

public class Solution {
    //小顶堆
    PriorityQueue<Integer> a = new PriorityQueue<>();
    //大顶堆
    PriorityQueue<Integer> b = new PriorityQueue<Integer>(100, new Comparator<Integer>(){
      @Override
      public int compare(Integer o1, Integer o2) {
        return o2 - o1;
      }
    });
    int index = 0;
    
    public void Insert(Integer num) {
        if(index%2 != 0){
            b.offer(num);
            a.offer(b.poll());
        }else{
            a.offer(num);
            b.offer(a.poll());
        }
       index++;
    }

    public Double GetMedian() {
        if(index % 2 == 0){
            return (a.peek() + b.peek())/2d;
        }else{
            return (double)b.peek();
        }
    }


}