剑指 Offer 41. 数据流中的中位数(大顶堆+小顶堆 求中位数)

剑指 Offer 41. 数据流中的中位数(困难)

问题描述

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

例如,

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

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

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

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

示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制:

    最多会对 addNum、findMedian 进行 50000 次调用。

解题思路

前面几道题的解题思路中提到,求最小的k个数和最大的k个数,这种题的最快解法就是维护一个大小为k的大顶堆(求前k小的数)或者小顶堆(求前k大的数)。

并且求一堆数的中位数,使用大顶堆+小顶堆的解法效率也远高于其他解法。具体思路如下:

(1)由于我们要求中位数,即需要把所有数分为左右两部分,左边的数是小于中位数的,右边的数是大于中位数的。

(2)如果这些数的总个数为偶数,那么中位数就是左边部分的最大值,和右边部分的最小值求和再除以2.

(3)如果这些数的总个数是奇数,那么如果我们始终优先维护左边部分,那么直接取左边部分的最大元素即可。

基于以上原理,我们需要构建两部分,并且能方便的得到左边部分所有元素的最大值,和右边部分所有元素的最小值。因此容易想到,只需要构建一颗大顶堆维护左边元素,再构建一颗小顶堆维护右边元素。并优先维护左边元素(保证奇数情况下,左边元素比右边元素多一个)。

因此,在加入元素时,考虑处理如下的情况:

①假如新元素num小于左边元素最大值(左边堆顶结构),说明num元素需要放在左边结构中,但原本元素个数是均匀的,左边部分新加入一个元素,说明需要匀一个元素到右边结构(堆顶元素)。保证左边元素和右边元素在顺序上以及个数上的稳定性。

②假如新元素num大于右边元素最小值(右边堆顶结构),说明num元素需要放在右边结构中,但原本元素个数是均匀的,右边机构中加入一个新元素,说明需要匀一个元素到左边结构(堆顶元素)。保证左边元素和右边元素在顺序上以及个数上的稳定性。

③当左边结构元素个数 与 右边结构元素个数不同时,说明此时元素个数为奇数个,应当优先保证左边结构元素个数 大于 右边结构元素个数。

Java代码

class MedianFinder {
    Queue<Integer> maxqueue;
    Queue<Integer> minqueue;
    /** initialize your data structure here. */
    public MedianFinder() {
        maxqueue = new PriorityQueue<>((o1,o2)->o2-o1);
        minqueue = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        maxqueue.offer(num);
        minqueue.offer(maxqueue.poll());
        if(minqueue.size() > maxqueue.size())
            maxqueue.offer(minqueue.poll());
    }
    
    public double findMedian() {
        if(maxqueue.size()>minqueue.size()) return maxqueue.peek();
        return (maxqueue.peek()+minqueue.peek())/2.0;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值