当我拿到这道题的时候,第一时间想到的就是如下的暴力解法,时间复杂度:O(nlogn)+O(1)≃O(nlogn),空间复杂度:O(n).
因为暴力解法每次都要对整个list做sort,时间复杂度是nlog(n), 对于大数据量的test case,会TLE.
class MedianFinder { private List<Integer> list = new ArrayList<>(); public MedianFinder() { } public void addNum(int num) { list.add(num); } public double findMedian() { Collections.sort(list); int size = list.size(); if(size%2==1){ return list.get(size/2); }else{ return (double)(list.get(size/2-1)+list.get(size/2))/2; } } }
既然这道题涉及到排序,那么我们自然会想到Heap,在java中,也就是PriorityQueue,但是这道题是求中间值,如何能快速找到PriorityQueue的中点呢?
如果只用一个PriorityQueue来存储数据,那么每次找中间值的过程的时间复杂度也是O(n), 再乘以PriorityQueue本身排序的时间复杂度,就又变成了O(nLog(n)).
我们能不能把查找中间值的时间复杂度从O(n),降到O(1)呢?答案是:Yes. 方法也很简单,只要用两个PriorityQueue就可以解决这个问题。
第一个PriorityQueue用来存储数值较小的一半数字,从大到小排序,第二个PriorityQueue用了存储数值较大的一半数字,从小到达排序。每次addNum的时候,把两个PriorityQueue调整一下即可。
时间复杂度O(logn), 空间复杂度O(n). 算法如下:
class MedianFinder { private PriorityQueue<Integer> smallQ; private PriorityQueue<Integer> largeQ; private boolean odd = false; public MedianFinder() { smallQ = new PriorityQueue<>((x,y)->y-x); largeQ = new PriorityQueue<>(); } public void addNum(int num) { if(odd){ largeQ.offer(num); smallQ.offer(largeQ.poll()); }else{ smallQ.offer(num); largeQ.offer(smallQ.poll()); } odd = !odd; } public double findMedian() { if(odd){ return largeQ.peek(); }else{ return (smallQ.peek()+largeQ.peek())/2.0; } } }
对于follow-up的两个问题,用bucket排序来做即可,具体的解决方案,我比较认可这个帖子的算法:
https://leetcode.com/problems/find-median-from-data-stream/discuss/286238/Java-Simple-Code-Follow-Up