2020-10-12(堆)

每天一道算法题:

41 . 数据流的中位数:
[2,3,4] 的中位数是 3

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

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

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

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

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

数据结构:
PriorityQueue,优先队列,底层数据结构是堆,默认小顶堆,即:
PriorityQueue defaultPQ = new PriorityQueue<>( (x,y) -> (x-y) ); //即堆顶是队列中最小的
大顶堆即( (x,y) -> (y-x) );

思路:
小顶堆用于存储较大的一般,堆顶即较大一半的“最小值”
大顶堆则反之,堆顶即较小一半的“最大值”。

找中位数-------当数据量为偶数的时候,这个最小值和最大值正好是最中间的两个数,当数据量为奇数的时候,也不用担心,令小顶堆的数量比大顶堆多1即可,然后中位数就是小顶堆的堆顶。

加入数据------ 中位数可以快速找到了,那新数据加入怎么办呢,很简单,和两个堆顶比较下就知道了,如果小于大顶堆顶,就加入大顶堆,其他情况加入小顶堆,但是要注意加入之后保证两个堆的均衡,因为规定小顶堆的数量 - 大顶堆数量 ∈ [0,1] 。

所以当大顶堆数量小于小顶堆时,才可以直接加入大顶堆,其他情况(两个堆数量相等)则把大顶堆的堆顶,(小半区老大)推出来加入小顶堆(大半区),这样制造出大顶堆数量比小顶堆数量小1,方可加入大顶堆。(小顶堆同理,相等的时候可以直接入,但是多1的时候得先弹一个到大顶堆,才能加)

算法简化:由于堆自带的堆顶最值显示,所以无需判断数据和两个堆顶的大小关系,而且如果恰好处于两者之间也无法解决,因此我们只考虑要加进哪个堆即可,“你的意思是不管数值多少直接加到你想加入的堆么”,是的!比如小顶堆要保持数量优势,我就令两个堆大小一样的时候,元素加入小顶堆,不一样(即小顶堆多1个)的时候,加入大顶堆。
有人要问了,这个元素如果不属于这个区呢?比如一个很小的数加入了小顶堆(大半区),都无所谓,元素加入了某个堆的时候,由于堆会自动排序,比如这个很小的数加入了小顶堆(大半区),那么他就会变成堆顶,把这个堆顶推给小半区的大顶堆,细心的你会发现,最后是大顶堆多了一个元素,小顶堆元素数量不变,这不是搞反了么,没事那就令相等的时候(比如一开始两个堆都是0),元素先进大顶堆,然后再将大顶堆堆顶弹出至小顶堆,这样小顶堆+1元素,就可以保证数量优势了。

伪代码:

PriorityQueue small , big
double findMedian() : //简单,两个堆数量一样返回各自堆顶平均数,不一样(小顶多1)返回小顶堆
   return  small.size == big.size ? (small.peek() + big.peek())/2  : small.peek()

void addNum(int num) 
  if small.size == big.size:
       big.add(num);
       small.add( big.poll() );
  else:
       small.add(num);
       big.add( small.poll() );

时间:超越28%人 内存:超越48%人
//注意,一定要自己尝试亲自实现堆排序

你可能感兴趣的:(2020-10-12(堆))