当我拿到这道题的时候,第一时间想到的就是如下的暴力解法,时间复杂度: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

posted on 2022-01-01 07:19  阳光明媚的菲越  阅读(24)  评论(0编辑  收藏  举报