面试题41:数据流中的中位数

有些话、适合烂在心里 提交于 2020-02-07 22:16:56

1、题目描述:

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

2、思路:看到这道题很自然的想到常规解法,对数组进行排序,也就是说只要插入一个数据,就对数组进行一次排序,就要移动后边的数字,因此插入数据的时间复杂度为o(n),而从排序数组中取出中位数的时间复杂度为O(1)。这种方法比较显然,还不是最终解法。

  而另一种解法就是用自平衡的二叉搜索树(AVL)树,通常AVL树的平衡因子是左右子树的高度差。可以稍作修改,把AVL的平衡因子改为左右子树的节点数目之差。这样插入新的节点的时间复杂度为O(logn),而取出中位数的时间复杂度为O(1)。而AVL树是没有库函数的,因此不好实现。

  最终的解法:可以先观察如下数组

N1  N2  ......  Nm  Nm+1 .......N2m  N2m+1

N1  N2  ......  Nm  Nm+1 .......N2m  

  上边的数组元素个数为奇数个,中位数是Nm+1

  下边的为偶数个元素,中位数是Nm+Nm+1除以2

  无论是奇数个元素还是偶数个,整个数组被分成了两部分,位于左边的元素都比位于右边的元素要小,另外Nm是左边最大的数,Nm+1是右边最小的数。

  因此,我们只需要用最大堆存左边的数,最小堆存右边的数,将整个数组的数进行一个平均分配,即可实现中位数的查找。

实现步骤:

  首先要保证两个堆中的元素的个数不能超过1,因此,为保证数据平均分配,可以在数据的总数是偶数时把数据插入最小堆,总数是奇数时将数据插入最大堆。

  还要保证最大堆中的数要都小于最小堆的数。当数据是偶数个时,把新数插入到最小堆中,这时最小堆中的数就会重排,为防止最小堆中存在比最大堆小的数,然后再从最小堆中取出堆顶元素,插入到最大堆中。

  插入最大堆的时候也同理。

  这样的来回调换,就保证了两个堆的大小关系。

3、代码:

import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
    //PriorityQueue<Integer> left = new PriorityQueue<>((o1,o2)->o2-o1);
    //PriorityQueue<Integer> left = new PriorityQueue<>(Comparator.reverseOrder());
    //最大堆
    PriorityQueue<Integer> left = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {                
            return o2-o1;
        }
    });
    //最小堆
    PriorityQueue<Integer> right = new PriorityQueue<>();
    
    private int N;
    public void Insert(Integer num) {
        if(N%2==0){
            left.add(num);
            right.add(left.poll());
        }else{
            right.add(num);
            left.add(right.poll());
        }
        N++;
    }
 
    public Double GetMedian() {
         if(N%2==0)
             return (left.peek()+right.peek())/2.0;
        else
            return (double)right.peek();
    }
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!