LeetCode刷题笔记 239 / 剑指Offer 59(涉及到双端队列)

题目:滑动窗口最大值

在这里插入图片描述
答案:

参考链接:详细通俗的思路分析,多解法

1.优先队列

建立大顶堆,保证堆顶是最大值。每次移除元素时间复杂度O(k)

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if (nums == null || n == 0 || k <= 0 || n < k) {
            return nums;
        }
        int result[] = new int[n - k + 1];
        int index = 0;
        PriorityQueue<Integer> q = new PriorityQueue(k, Comparator.reverseOrder());
        for (int i = 0; i < n; i++) {
            if (q.size() >= k) {   //移除上一个窗口的第一个元素
                q.remove(nums[i - k]);
            }
            q.add(nums[i]);
            if (i >= k - 1) {      //取最大值,更新result
                result[index++] = q.peek();
            }
        }
        return result;
    }
}

注意:
PriorityQueue默认为升序(小顶堆)
升序:Comparator.naturalOrder()
降序:Comparator.reverseOrder()

2.双端队列

ArrayDeque继承自Deque接口,可以从队列首部插入/取出,也可以从队列尾部插入/取出。

保持队列单调递减(队首元素即为最大值):
如果当前元素比队列的最后一个元素大,那么就将最后一个元素出队,重复这步直到当前元素小于队列的最后一个元素或者队列为空。
如果当前元素小于等于队列的最后一个元素或者队列为空,那么就直接将当前元素入队。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> max = new ArrayDeque<>();
        int n = nums.length;
        if (n == 0) {
            return nums;
        }
        int result[] = new int[n - k + 1];
        int index = 0;
        for (int i = 0; i < n; i++) {
            if (i >= k) {   //判断队首元素是否是上一滑动窗的第一个元素,是则删除
                if (max.peekFirst() == nums[i - k]) {
                    max.removeFirst();
                }
            }
            while (!max.isEmpty() && nums[i] > max.peekLast()) {  //保持队列单调递减
                max.removeLast();
            }

            max.addLast(nums[i]);
            if (i >= k - 1) {       //队首元素即为最大值,更新result
                result[index++] = max.peekFirst();
            }
        }
        return result;
    }
}

3.动态规划

把数组 k 个一组就行分组。

nums = [1,3,-1,-3,5,3,6,7], and k = 3
    
| 1 3 -1 | -5 4 3 | 5 7 |    
    
如果我们要求 result[1],也就是下边 i 到 j 范围内的数字的最大值

| 1 3 -1 | -5 4 3 | 5 7 |   
    ^       ^
    i       j
i 到 j 范围内的数字被分割线分成了两部分

如果我们知道了左半部的最大值和右半部分的最大值,那么两个选最大的即可。
左半部分的最大值,也就是当前数到它右边界范围内的最大值。
用 rightMax[i] 保存 i 到它的右边界范围内的最大值,只需要从右到左遍历一遍就可以求出来了。
同理,右半部分的最大值,也就是当前数到它左边界范围内的最大值。
用 leftMax[i] 保存 i 到它的左边界范围内的最大值,只需要从左到右遍历一遍就可以求出来。

有了上边的两个数组,当前范围的两个边界 i 和 j,rightMax[i] 存储的就是左半部分(i 到右边界)的最大值,leftMax[j] 存储的就是右半部分(j 到左边界)的最大值。result[i] 的结果也就出来了。

result[i] = Math.max(rightMax[i], leftMax[j]);

具体举例:

nums = [1,3,-1,-3,5,3,6,7], and k = 3
            | 1 3 -1 | -5 4 3 | 5 7 |    
rightMax:     3 3 -1    4 4 3   7 7  (从右往左计算更新)
leftMax:      1 3  3   -5 4 4   5 7  (从左往右计算更新)
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if (n == 0) {
            return nums;
        }

        //当前数到自己的左边界的最大值
        int leftMax[] = new int[n];
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < n; i++) {
            if (i % k == 0) {      //每到一个边界就重置最大值
                max = Integer.MIN_VALUE;
            }
            if (max < nums[i]) {
                max = nums[i];
            }
            leftMax[i] = Math.max(nums[i], max);
        }

        //当前数到自己的右边界的最大值
        int rightMax[] = new int[n];
        max = Integer.MIN_VALUE;
        for (int i = n - 1; i >= 0; i--) {
            if (i % k == 0) {      //每到一个边界就重置最大值
                max = Integer.MIN_VALUE;
            }
            if (max < nums[i]) {
                max = nums[i];
            }
            rightMax[i] = Math.max(nums[i], max);
        }

        int result[] = new int[n - k + 1];
        for (int i = 0; i < result.length; i++) {
            int j = i + k - 1;
            result[i] = Math.max(rightMax[i], leftMax[j]);
        }
        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值