LeetCode - 480 滑动窗口中位数 (滑动窗口 + 优先队列)

这篇博客探讨了如何使用双堆数据结构(大根堆和小根堆)来解决寻找滑动窗口中位数的问题。在算法实现中,作者详细解释了双堆的插入、删除和平衡操作,并指出了一种删除元素的方法可能导致测试用例失败的原因。通过改进删除方法,确保堆顶元素不处于延迟删除状态,从而保证了算法的正确性。
摘要由CSDN通过智能技术生成

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意有个问题 return这两种写法有什么区别吗 用被注释的那种就会有测试用例通不过
在这里插入图片描述
在这里插入图片描述

class Solution {
    public double[] medianSlidingWindow(int[] nums, int k) {
         int n = nums.length;
         double[] ans = new double[n-k+1];
         DualHeap dh = new DualHeap(k);
         for(int i = 0; i < k;i++){
            dh.addElement(nums[i]);
         }
         int index = 0;
         ans[index++] = dh.getMid();
         for(int i = k; i < n;i++){
            dh.removeElement(nums[i-k]);
             dh.addElement(nums[i]);
             ans[index++] = dh.getMid();
         }
         return ans;
    }
    class DualHeap{
        //大根堆
        private PriorityQueue<Integer> maxHeap;
        //小根堆
        private PriorityQueue<Integer> minHeap;
        // 窗口大小
        private int k;
        //删除表 当窗口往后移动时 某些元素退出窗口 它们要被删除
        //采用延迟删除策略 先在删除表中记录 只有当该元素出现在大根堆或小根堆堆顶的时候才删除
        private Map<Integer,Integer> delete;
        //大根堆实际大小 因为存在延迟删除
        private int maxHeapSize;
        //小根堆实际大小
        private int minHeapSize;

        public DualHeap(int k){
           minHeap = new PriorityQueue<>(new Comparator<Integer>(){//小根堆
               public int compare(Integer i1,Integer i2){
                 // return i1 - i2;
                  return i1.compareTo(i2);
               }
           });
           maxHeap = new PriorityQueue<>(new Comparator<Integer>(){ //大根堆
                 public int compare(Integer i1,Integer i2){
                    //return i2 - i1;
                    return i2.compareTo(i1);
                }
            });
            this.k = k;
            this.delete = new HashMap<>();
            this.maxHeapSize = 0;
            this.minHeapSize = 0;
        }
        public double getMid(){
            //返回中位数
            return k % 2 == 0 ? ((double)minHeap.peek() + maxHeap.peek()) / 2 : maxHeap.peek();
        }
        //删除一个元素
        //这样会有测试用例无法通过
        // public void removeElement(int d){
            //单纯删除 可能会导致移除该堆顶后堆顶的下一个数也是要延迟删除的 
            //在makeblance时 将延迟删除的数 误当作 中位数候选数 要永远保持堆顶绝对不能是延迟删除的数
        //     if(minHeap.size() != 0 && minHeap.peek() == d){//小根堆堆顶 直接删除该数
        //         minHeap.poll();
        //         minHeapSize -= 1;
        
        //     }
        //     else if(maxHeap.size() != 0 && maxHeap.peek() == d){//大根堆堆顶直接删除该数
        //         maxHeap.poll();
        //         maxHeapSize -= 1;
        //     }
        //     else{
        //         //无法直接删除 记录在删除表中 延迟删除
        //         delete.put(d,delete.getOrDefault(d,0)+1);
        //         if(d < maxHeap.peek())
        //            maxHeapSize -= 1;
        //         else{
        //             minHeapSize -= 1;
        //         }
        //     }
        //      makebalance();
        // }
         public void removeElement(int num) {
         delete.put(num, delete.getOrDefault(num, 0) + 1);
        if (num <= maxHeap.peek()) {
            --maxHeapSize;
            //单纯删除 可能会导致移除该堆顶后堆顶的下一个数也是要延迟删除的 
            //在makeblance时 将延迟删除的数 误当作 中位数候选数 要永远保持堆顶绝对不能是延迟删除的数
            if (num == maxHeap.peek()) {
                prune(maxHeap);
            }
        } else {
            --minHeapSize;
            if (num == minHeap.peek()) {
                prune(minHeap);
            }
        }
        makebalance();
    }
        // 添加一个元素
        public void addElement(int d){
            if(maxHeap.size() == 0 || d <= maxHeap.peek()){//小于等于大根堆堆顶 或者大根堆为空(优先放入大根堆)
               // 存放在小根堆
               maxHeap.offer(d);
               maxHeapSize += 1;
            }
            else{//大于小根堆堆顶
                minHeap.offer(d);
                minHeapSize += 1;
            }
            makebalance();
        }
        public void prune(PriorityQueue<Integer> heap){
            //堆顶出现了delete表中的值
            while(heap.size()!=0 && delete.getOrDefault(heap.peek(),0) > 0){
                  int peek = heap.peek();
                  delete.put(peek,delete.getOrDefault(peek,0) -1);
                  if(delete.getOrDefault(peek,0) == 0){
                      delete.remove(peek);
                  }
                  heap.poll();
            }
        }
        public void makebalance(){
            //异常情况 
            //小根堆比大根堆数多一
             if(minHeapSize > maxHeapSize){
                  maxHeap.offer(minHeap.poll());
                  maxHeapSize += 1;
                  minHeapSize -=1;
                  prune(minHeap);
             }
             //大根堆比小根堆数多二
             else if(maxHeapSize > minHeapSize + 1){
                  minHeap.offer(maxHeap.poll());
                  maxHeapSize -= 1;
                  minHeapSize +=1;
                  prune(maxHeap);
             }

        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值