求中位数?

如何求一个中位数?

如果输入一个数组,让你求中位数,简单的解决方法就是对数组进行排序

如果数组长度是奇数,最中间的一个元素就是中位数

如果数组长度是偶数,最中间两个元素的平均数作为中位数。

但是如果数据规模非常巨大,排序不太现实,那么也可以使用概率算法,随机抽取一部分数据,排序,求中位数,作为所有数据的中位数。

295. 数据流的中位数

这道题就是让我们设计这样一个类:

class MedianFinder {
   

    // 添加一个数字
    public void addNum(int num) {
   }

    // 计算当前添加的所有数字的中位数
    public double findMedian() {
   }
}

常规思路:

常规的思路就是

用一个数组记录所有addNum添加进来的数字,通过插入排序的逻辑保证数组中的元素有序,当调用findMedian方法时,可以通过数组索引直接计算中位数

但是用数组作为底层容器的问题也很明显:

addNum搜索插入位置的时候可以用二分搜索算法,但是插入操作需要搬移数据,最坏时间复杂度为O(N)

因为数组的插入时间复杂度问题,考虑使用链表

使用链表插入数据很快,但是查找插入位置的时候只能线性遍历,最坏时间复杂度为 O(N)

而且findMedian方法也需要遍历寻找中间索引,最坏时间复杂度也是 O(N)

因为链表的查找时间复杂度问题,考虑使用平衡二叉树

平衡二叉树的增删查改的复杂度都为 O(logN)

比如使用Java提供的TreeSet容器,底层是红黑树,addNum直接插入,findMedian可以通过当前元素的个数推算出计算中位数元素的排名

但是,TreeSet是一种Set其中不存在重复元素的元素,但是我们的数据流可能输入重复数据,而且计算中位数也是需要算上重复元素的

不仅如此,TreeSet并没有实现一个通过排名快速计算元素的API。也就是说加入我们想找到TreeSet中的第5大的元素我们需要手动去实现这个需求

平衡二叉树也不行,那**优先级队列(二叉堆)**可以吗?

优先级队列是一种受限的数据结构,只能从堆顶添加/删除元素,我们的addNum方法可以从堆顶插入元素,但是findMedian函数需要从数据中间取,这个功能优先级队列是没办法提供的

解决思路:

解决这个问题我们必然是会用到有序数据结构的,本题所用到的数据结构是两个优先级队列

中位数是有序数组最中间的元素

我们可以将有序数组抽象成一个倒三角形(从大到小),宽度可以视为元素的大小,那么这个倒三角形的中部就是计算中位数的元素

将这个倒三角形从中间切成两半,编程一个小倒三角形和一个梯形

这个小的倒三角形相当于一个从小到大的有序数组,这个梯形相当于一个从大到小的有序数组

他们分别可以是大顶堆和小顶堆,中位数就是他们的堆顶元素

但是梯形虽然是小顶堆,但其中的元素是较大的,我们称其为large,倒三角虽然是大顶堆,但是其中元素较小,我们称其为small

当然,这两个堆需要算法逻辑正确维护,才能保证堆顶元素是可以算出正确的中位数,我们很容易看出来,两个堆中的元素之差不能超过 1。(这其实就是在限制实现addNum方法)

假设元素总数是n

  • 如果n是偶数,我们希望两个堆的元素个数是一样的,这样把两个堆的堆顶元素拿出来求个平均数就是中位数;
  • 如果n是奇数,那么我们希望两个堆的元素个数分别是n/2 + 1n/2,这样元素多的那个堆的堆顶元素就是中位数。

因此,我们可以得到代码如下:

class MedianFinder {
   

    private PriorityQueue<Integer> large;
    private PriorityQueue<Integer> small;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值