【滑动窗口】力扣239.滑动窗口最大值

前面的文章我们练习数十道 动态规划 的题目。相信小伙伴们对于动态规划的题目已经写的 得心应手 了。

还没看过的小伙伴赶快关注一下,学习如何 秒杀动态规划 吧!

接下来我们开启一个新的篇章 —— 「滑动窗口」

滑动窗口

滑动窗口 是一种基于 双指针 思想的算法。两个指针指向的元素之间会形成一个窗口,从前往后遍历元素进行一定的运算。

从名字中也不难看出:滑动 说明窗口的大小并不是固定不变的。通过左右指针按照 规则 向前滑动形成的不同大小的窗口解决具体的问题。

因此,分析问题的 关键 就在于 明确两个指针的移动规则

核心步骤

  1. 初始时,L = R = 0 ,并规定窗口的取值为 [L, R) 即:左闭右开 。因此,初始时[0, 0) 无意义,窗口内没有任何元素被包含。

  2. 循环遍历,在保证不会越界的情况下,不断 增大右指针R。当满足要求后,停止增大右指针R

  3. 左指针L开始 不断增大,直到不满足要求后停下。

  4. 重复执行 2、3 两步,直到右指针R走到尽头( 越界 )。

左右指针轮流向前移动,窗口大小不断变化。新旧元素加入和移出后,及时更新该窗口范围内的相关数据。当执行结束后,收集到了所有符合要求的数据,且时间复杂度降低到了 O ( N ) O(N) O(N)


下面我们通过一道 力扣 Hard 题 进行学习。

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的 最大值


在解决该题目之前,我们先来讨论一下这类题的套路。

思路与套路

对于 窗口内最大最小值 的题目,我们采用 双端队列 的结构进行思考。并对 出队入队 进行这样的规定(以窗口内的最大值举例):

队列含义:
  1. 如果此时开始缩窗口(即:L++),哪些值会依次成为此时窗口内的最大值。
  2. 要求,队列中的元素从大到小排列。
队列的出入:

队尾 的出队与入队:

  • 如果队空,直接入队;
  • 否则,如果队尾位置不大于当前来到位置的数,从队尾出队。直到队尾元素大于当前位置元素,从队尾入队。

队头 的出队:

  • 当窗口缩小时,若队头元素已经不在当前的窗口中时,从队头弹出即可。

是不是没搞懂在说什么,没关系。我们通过具体的例子感受该流程:

就拿例 1 的例子,nums=[1,3,-1,-3,5,3,6,7],构造双端队列的值依次为:

注意: 双端队列中 队头元素 保存的是 当前范围最大值

先看从队尾出入队的情况:



以上均是 L 不动,R 在右移的情况。

再看从队头出队的情况:

下面我们再来看 L 也向右移动的情况:

L 指向 1,R 指向 5 时为例:


跟着图细心体会该过程哦!相信小伙伴能够理解双端队列的出入规则了!


下面我们回归正题,解决上面力扣 滑动窗口最大值 的题目。

仔细思考后发现,其实就是将上面的过程 加以限制

限制了窗口的大小必须为 k 。

直接上代码。

代码

public static int[] getMaxWindow(int[] arr, int w) {
    if (arr == null || w < 1 || arr.length < w) {
        return null;
    }
    // 双端队列
    LinkedList<Integer> qmax = new LinkedList<Integer>();
    int N = arr.length;
    // 最终会产生 N - w + 1 个结果
    int[] res = new int[N - w + 1];
    // res 数组的下标
    int index = 0;
    // R 从左到右遍历数组,不回退
    for (int R = 0; R < N; R++) {
        // 双端队列中有值,并且 队尾的数 比当前的 小 就弹出
        while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) {
            qmax.pollLast();
        }
        // 双端队列为空,或者弹出完比自己小的数了
        // 从末尾插入队列中
        qmax.addLast(R);
        // R-w 是过期的下标,即 L 的位置
        // 队头的下标 过期了就弹出
        if (qmax.peekFirst() == R - w) {
            qmax.pollFirst();
        }
        // 能够形成完整的窗口了,开始往 结果数组 里填最终答案
        if (R >= w - 1) {
            res[index++] = arr[qmax.peekFirst()];
        }
    }
    return res;
}

代码解释

其实注释已经写的非常清楚了。

  1. 在上面的图中,我么在双端队列中放入的是 元素值,但在实际的代码中,存入的是下标,这样的话既能够比较大小,又能方便的进行入队出队操作。
  2. 窗口大小固定,R 和 L 一起右移。因此若判断出队头元素已经离开了窗口,就要弹出。
  3. 刚开始,R 从 0 开始增大,窗口还未形成。只有当形成 k 大小的窗口后再开始更新答案。(那 思考一下 为什么不直接从下标 k 开始呢?这不直接就有窗口了么?欢迎留言评论 ~)

~ 点赞 ~ 关注 ~ 星标 ~ 不迷路 ~!!!

关注回复「ACM紫书」获取 ACM 算法书籍~

  • 31
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值