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(); } }
来源:https://www.cnblogs.com/guoyu1/p/12274753.html