剑指 Offer 41. 数据流中的中位数

这道题以前就刷过,感觉一直稀里糊涂的,导致再做一次的话,即时知道要用两个堆,以及一些思路,细节还是处理不好。

方法细节不一定是最好的,但是下面这个思路是我自己可以理解且不会忘记的,记录一下。

————————————

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

————————————

大体思路很明确,因为中位数是排序之后,位于中间的数值,所以以他为界分为2个数组的话,在他之前的数都是比他小的,且他是之前数组的最大值;同理,在他之后的数都是比他大的,且他是之后数组的最小值,很自然想到前面一个大根堆,后面一个小根堆。

我们需要做的是,

(1)维持这两个堆的容量的平衡。

如果当前数据流是偶数个数,那么这两个堆的容量相等,中位数这两个堆的堆顶的平均值;如果是奇数个数,那么必然有一个堆要多一个数,多出来的这个数就是中位数。

(2)维持小根堆里的数大小都比大根堆里的数大。

 

这里可以随意指定一个堆的容量多1,假设我选取大根堆的容量多1个,即大根堆容量-小根堆容量<=1.

那么当addNum()时,我需要做的事情是:

(1)先什么都不管,添加到大根堆中。

(2)如果大根堆的size - 小根堆的size>1了,那么需要将大根堆的堆顶放到小根堆中,调整两个堆的大小。

(此举是保证容量的平衡)

(3)如果添加的这个Num比小根堆的堆顶要大的话,那么将他与小根堆的堆顶交换。

   (因为Num已在第一步添加到大根堆中了,又Num比小根堆堆顶要大,又小根堆所有数都比大根堆中的数要大,所以这个Num肯定在大根堆的堆顶,所以此举其实就是交换两个堆的堆顶,目的是为了保证小根堆里的数都比大根堆中的数大。)

需要注意的就是第二步只会在大根堆的size已经比小根堆的size多1的时候做,所以这步做完,两个堆的大小肯定相等,而且如果Num是比小根堆的堆顶大的话,他肯定会在这步移动到小根堆中,所以做完第二步可以不用做第三步了,直接return。第三步是在大根堆的size与小根堆的size一样时候做的,以免出现大根堆中有比小根堆大的数。

class MedianFinder {
    PriorityQueue<Integer> minHeap;
    PriorityQueue<Integer> maxHeap;

    public MedianFinder() {
        minHeap = new PriorityQueue();
        maxHeap = new PriorityQueue<>((x,y)->(y-x));
    }
    
    public void addNum(int num) {
         maxHeap.add(num);
         if(maxHeap.size()-minHeap.size()>1) {
             minHeap.add(maxHeap.poll());
             return;
         }
         if(minHeap.size()!=0 && num > minHeap.peek()) {
             int temp = minHeap.poll();         
             minHeap.add(maxHeap.poll());
             maxHeap.add(temp);
         }
    }
    
    public double findMedian() {
        if(maxHeap.size()==0) throw new RuntimeException("无数字!");
        else if(minHeap.size() == maxHeap.size()) return (minHeap.peek()+maxHeap.peek())/2.0;
        else return maxHeap.peek();
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值