[LC] 480. Sliding Window Median

https://leetcode.com/problems/sliding-window-median/

这题就是这一题的延伸版本:https://blog.csdn.net/chaochen1407/article/details/82688222

可以直接在这一题的原做法上加一个删除的操作即可。就是当这个window走过一个元素,就从两边的queue中某个删除掉就可以了。但是从priority queue里面删掉一个元素的过程是O(k),所以整个操作的复杂度就变成了O(nk)了,给出代码如下:

    public double[] medianSlidingWindow(int[] nums, int k) {
        PriorityQueue<Integer> minQueue = new PriorityQueue<>(k, Collections.reverseOrder());
        PriorityQueue<Integer> maxQueue = new PriorityQueue<>();
        double[] result = new double[nums.length - k + 1];
        for (int i = 0; i < nums.length; i++) {
            if (minQueue.size() < maxQueue.size()) {
                minQueue.add(nums[i]);
            } else {
                maxQueue.add(nums[i]);
            }
            
            if (!minQueue.isEmpty() && minQueue.peek() > maxQueue.peek()) {
                int tmp = minQueue.poll();
                minQueue.add(maxQueue.poll());
                maxQueue.add(tmp);
            }
            
            if (i >= k - 1) {
                if (k % 2 == 0) {
                    result[i - k + 1] = minQueue.peek() / 2.0 + maxQueue.peek() / 2.0;
                } else {
                    result[i - k + 1] = (double)maxQueue.peek();
                }
                
                if (minQueue.isEmpty() || nums[i - k + 1] > minQueue.peek()) {
                    maxQueue.remove(nums[i - k + 1]);
                } else {
                    minQueue.remove(nums[i - k + 1]);
                }
            }
        }
        
        return result;
    }

其实这个操作的复杂度还是可以从O(nk)变回O(nlogk)的,用multiset替代priorityqueue即可。只是这个数据结构只有C++有,Java没有一个现成的数据结构,用TreeSet的话没办法处理相同的元素,所以我们只能自己弄一个MultiSet,其实用TreeMap做开发即可。

    class MultiSortedSet {
        TreeMap<Integer, Integer> map;
        int size;
        public MultiSortedSet() {
            this.map = new TreeMap<>();
            this.size = 0;
        }
        
        public int size() {
            return this.size;
        }
        
        public int getLargest() {
            if (this.size == 0) return 0;
            return map.lastKey();
        }
        
        public int getSmallest() {
            return this.size == 0 ? 0 : map.firstKey();
        }
        
        private int _removeEntry(Map.Entry<Integer, Integer> entry) {
            Integer count = entry.getValue();
            Integer val = entry.getKey();
            count--;
            this.size--;
            if (count == 0) {
                this.map.remove(val);
            } else {
                this.map.put(val, count);
            }
            
            return val;            
        }
        
        private int _removeSideEntry(boolean isEnd) {
            if (this.size == 0) return 0;
            Map.Entry<Integer, Integer> entry = isEnd ? map.lastEntry() : map.firstEntry();
            return this._removeEntry(entry);
        }
        
        public int removeLargest() {
            return this._removeSideEntry(true);
        }        
        
        public int removeSmallest() {
            return this._removeSideEntry(false);
        }
        
        public int remove(int val) {
            if (this.size == 0) return 0;
            return this._removeEntry(this.map.ceilingEntry(val));
        }
        
        public void add(int val) {
            this.size++;
            this.map.put(val, this.map.getOrDefault(val, 0) + 1);
        }
    }

    public double[] medianSlidingWindow(int[] nums, int k) {
        MultiSortedSet leftSet = new MultiSortedSet();
        MultiSortedSet rightSet = new MultiSortedSet();
        double[] result = new double[nums.length - k + 1];
        for (int i = 0; i < nums.length; i++) {
            if (rightSet.size() > leftSet.size()) {
                leftSet.add(nums[i]);
            } else {
                rightSet.add(nums[i]);
            }
            
            if (i >= 1 && rightSet.getSmallest() < leftSet.getLargest()) {
                int moveToRight = leftSet.removeLargest();
                leftSet.add(rightSet.removeSmallest());
                rightSet.add(moveToRight);
            }
            
            if (i >= k - 1) {
                if (k % 2 == 0) {
                    result[i - k + 1] = leftSet.getLargest() / 2.0 + rightSet.getSmallest() / 2.0;
                } else {
                    result[i - k + 1] = (double)rightSet.getSmallest();
                }
                
                int candidate = nums[i - k + 1];
                if (candidate <= leftSet.getLargest()) {
                    leftSet.remove(candidate);
                } else {
                    rightSet.remove(candidate);
                }
            }            
        }
        
        return result;
    }

只是要注意的是,虽然同样是O(logN)的操作,但是TreeSet的常数级别要比PriorityQueue要高。所以上面这个理论上O(nlogk)的代码跑起来居然比PriorityQueue的O(nk)的代码要慢。所以其实如果能够PriorityQueue完成的操作,还是不要用TreeSet或者TreeMap来做,有很多题都验证了后两者比前者要慢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值