目录
295. 数据流的中位数Find the Median of a Number Stream (medium)
480. 滑动窗口中位数Sliding Window Median (hard)
很多问题中,我们被告知,我们拿到一大把可以分成两队的数字。为了解决这个问题,我们感兴趣的是,怎么把数字分成两半?使得:小的数字都放在一起,大的放在另外一半。双堆模式就能高效解决此类问题。
正如名字所示,该模式用到了两个堆,是不是很难猜?一个最小堆用来找最小元素;一个最大堆,拿到最大元素。这种模式将一半的元素放在最大堆中,这样你可以从这一堆中秒找到最大元素。同理,把剩下一半丢到最小堆中,O(1)时间找到他们中的最小元素。通过这样的方式,这一大堆元素的中位数就可以从两个堆的堆顶拿到数字,从而计算出来。
判断双堆模式的秘诀:
- 这种模式在优先队列,计划安排问题(Scheduling)中有奇效
- 如果问题让你找一组数中的最大/最小/中位数
- 有时候,这种模式在涉及到二叉树数据结构时也特别有用
经典力扣题
295. 数据流的中位数Find the Median of a Number Stream (medium)
class MedianFinder {
private PriorityQueue<Integer> maxheap;
private PriorityQueue<Integer> minheap;
/** initialize your data structure here. */
public MedianFinder() {
maxheap=new PriorityQueue<>((x,y)->y-x);//java中PriorityQueue是小顶堆,小数在前,我们让其逆序即为大顶堆
minheap=new PriorityQueue<>();
}
//让这个数在大顶堆中走一下,再将大顶堆中最大的那个数入小顶堆,这个逻辑会导致奇数时maxheap的size小于minheap,所以要写判断语句奇数情况下要还一个回去
public void addNum(int num) {
maxheap.add(num);
minheap.add(maxheap.poll());
if((maxheap.size()+minheap.size())%2==1){
maxheap.add(minheap.poll());
}
}
public double findMedian() {
if((maxheap.size()+minheap.size())%2==1){
return maxheap.peek();
}else{
return ((double)maxheap.peek()+minheap.peek())/2;
}
}
}
480. 滑动窗口中位数Sliding Window Median (hard)
错误解法
原因:维护窗口需要删除left元素,这在堆中十分困难,而本解法用maxheap.remove(nums[left-1]),错误理解了remove的含义
//暴力:每次对窗口进行排序
//双堆
class Solution {
public double[] medianSlidingWindow(int[] nums, int k) {
int len=nums.length;
double[] ans=new double[len-k+1];
PriorityQueue<Integer> maxheap=new PriorityQueue<> ((x,y)->y-x);//大顶堆,大数在前
PriorityQueue<Integer> minheap=new PriorityQueue<> ();//小顶堆
//大顶堆中的数都比小顶堆的小,且大顶堆的size大于等于小顶堆
boolean x=(maxheap.size()+minheap.size())%2==0;
int left=0,right=k-1;
for(int i=0;i<k-1;i++){
maxheap.add(nums[i]);
minheap.add(maxheap.poll());
maxheap.add(minheap.poll());
}
while(right<len){
if(left!=0){
if(nums[left-1]<=ans[left-1]){//说明在大顶堆
maxheap.remove(nums[left-1]);
}else{
minheap.remove(nums[left-1]);//移除left那个数没有这么简单,这个函数不是这么用的
if(maxheap.size()>minheap.size()+1){
minheap.add(maxheap.poll());
}
}
maxheap.add(nums[right]);
minheap.add(maxheap.poll());
maxheap.add(minheap.poll());
}
double med=0;
if(x){
med=(double)(maxheap.peek()+minheap.peek())/2;
}else{
med=maxheap.peek();
}
ans[left]=med;
left++;
right++;
}
return ans;
}
}
正确解法
延迟删除:在 295 题中,我们使用两个堆寻找中位数,如果两个堆是平衡的,那么我们可以从堆顶元素得到中位数。在本题中,这个性质同样成立,即使堆中有多余的元素(即应当被移除的元素),但只要两个堆是平衡的,我们仍然可以从堆顶得到中位数。我们可以使用哈希表来标记所有被移除的无效元素,如果某个堆的堆顶是一个无效元素,我们才会把它删除。
Maximize Capital (hard)
力扣题