题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
解题思路
思路一:使用容器,排序
见代码:
import java.util.*; // 运行时间:23ms // 占用内存:9104k public class Solution { ArrayList<Integer> res = new ArrayList<>(); public void Insert(Integer num) { res.add(num); Collections.sort(res);/ //或者把排序代码放在第二函数里面 能好些 } public Double GetMedian() { int n = res.size(); if (n % 2 == 0) { return Double.valueOf((res.get(n / 2) + res.get(n / 2 - 1)) / 2.0); } else { return Double.valueOf(res.get(n / 2)); } } }
思路二:使用最大最小堆
代码复杂:
分析:对于海量数据和流的数据,用最大堆和最小堆来管理
我们希望 数据分为[小]|[大]两个部分,细化一点
[最大堆 | 左边最大 leftMax] [右边最小rightMin | 最小堆]定义一个规则:保证左边和右边个数相差不大于1,且左边小于右边
1.数据是偶数的时候 insert的数据进入 [右边,最小堆]中
1.1当插入的数字cur > leftMax时,直接插入到[右边,最小堆]中
1.2当插入的数字cur < leftMax时,为了保证左边小于右边,
先把cur插入[最大堆|左边最大leftMax]中,
然后把leftMax放入[右边最小rightMin|最小堆]中
就可以保证: 左边 < 右边
2.数据是奇数的时候 insert的数据进入 [左边,最大堆]中
2.1当插入的数字cur < rightMin时,直接插入到[左边,最小堆]中
2.2当插入的数字cur > rightMin时,为了保证左边小于右边,
先把cur插入[右边最小rightMin|最小堆]中,
然后把rightMin放入[最大堆|左边最大leftMax]中
就可以保证: 左边 < 右边
最后:
如果是偶数:中位数mid= (leftMax+right)/2
如果是奇数:中位数mid= leftMax 因为先插入到左边,再插入到右边,为奇数时,中位数就是mid
代码如下://运行时间:15ms //占用内存:9852k import java.util.PriorityQueue; import java.util.Comparator; public class Solution { private PriorityQueue<Integer> min = new PriorityQueue<Integer>(); //使用lambda表达式 // private static PriorityQueue<Integer> maxHeap = new PriorityQueue<>((x, y) -> y - x); private PriorityQueue<Integer> max = new PriorityQueue<Integer>(15,new Comparator<Integer>() { public int compare(Integer o1, Integer o2) { return o2.compareTo(o1); } }); private int count=0; public void Insert(Integer num) { count++; //让如果insert的数是奇数,则先插入到大顶堆中,然后取大顶堆堆顶插入到小顶堆中 //始终保证小顶堆中的元素个数 大于等于 大顶堆中元素个数,即最多相差1 if(count%2==1){ max.offer(num); min.offer(max.poll()); }else{ min.offer(num); max.offer(min.poll()); } } public Double GetMedian() { if(count%2 ==0) return (min.peek()+max.peek())/2.0; else return Double.valueOf(min.peek()); } }
另一种代码
//降序就是最大堆,lambda表达式了解下 private static PriorityQueue<Integer> leftHeap = new PriorityQueue<>((x, y) -> y - x); //升序就是最小堆 private static PriorityQueue<Integer> rightHeap = new PriorityQueue<>(); //当前是奇数还是偶数 private static boolean isOdd = true; public static void Insert(Integer num) { if(isOdd) {//数据是奇数的时候 insert的数据进入 [左边,最大堆]中 if(leftHeap.isEmpty()) { leftHeap.add(num); } else {//这个时候rightHeap一定不是null,就不讨论了。考虑鲁棒性可以讨论 int rightMin = rightHeap.peek(); if(num <= rightMin) {//直接插入到[左边,最小堆]中 leftHeap.add(num); }else { rightHeap.add(num);//先把cur插入[右边最小rightMin|最小堆]中 leftHeap.add(rightHeap.poll());//然后把rightMin放入[最大堆|左边最大leftMax]中 } } }else {//数据是偶数的时候 insert的数据进入 [右边,最小堆]中 //这个时候leftHeap一定不是null,就不讨论了。考虑鲁棒性可以讨论 int leftMax = leftHeap.peek(); if(num >= leftMax) {//直接插入到[右边,最小堆]中 rightHeap.add(num); }else { leftHeap.add(num);//先把cur插入[右边最小rightMin|最小堆]中, rightHeap.add(leftHeap.poll());//然后把rightMin放入[最大堆|左边最大leftMax]中 } } isOdd = !isOdd;//改变奇偶性 } public static Double GetMedian() { if(leftHeap.isEmpty()) return 0.0; if(!isOdd)//这里插入num改变了奇偶性,这里是奇数的意思 return leftHeap.peek() / 1.0; else return (leftHeap.peek() + rightHeap.peek()) / 2.0; }
参考博客
https://sunweiguo.github.io/2019/03/18/%E5%89%91%E6%8C%87offer/%E3%80%90%E9%9D%A2%E8%AF%95%E9%A2%9863-%E6%95%B0%E6%8D%AE%E6%B5%81%E4%B8%AD%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0%E3%80%91/
https://www.nowcoder.com/questionTerminal/9be0172896bd43948f8a32fb954e1be1?f=discussion