leetcode:239. 滑动窗口最大值

题目来源

题目描述

在这里插入图片描述

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {

    }
};

题目解析

题目相当于维护了一个大小为3的滑动窗口,并输出每个滑动窗口内的最大值

在这里插入图片描述

那么什么是滑动窗口呢?

举个例子,我们从数组中第一个元素开始遍历,由于窗口的大小是 3 3 3,因此当遍历到第三个元素时候窗口就形成了

请添加图片描述
之后,继续遍历元素,为了保持窗口的大小为3,左侧元素就需要从窗口中剔除。这样就使得窗口一直向右移动,直到考察到最后一个元素结束,这就是所谓的滑动窗口。

请添加图片描述

怎么实现呢?我们分别维护两个变量L,R为表示一个窗口。那么,应该怎么滑动呢?令N = arr.size()

  • 刚开始时,L和R均指向-1,表示尚未开始滑动(L和R都是索引值而不是元素值)
  • 什么时候L滑动呢,什么时候R滑动呢?什么时候停止呢?最大能够给滑动到哪里呢?
  • 谁先滑动?是L先滑动还是R先滑动,还是同时滑动
    • 同时滑动肯定是不行的,因为这样窗口大小就一直为0了
    • 因为窗口不能是负值,所以R必须保证永远大于等于L,R应该先滑动
  • L和R滑动有什么意义?
    • 每次R往前移动一个位置,就表示有一个元素从右侧进入了窗口,窗口大小加一
    • 每次L往前移动一个位置,就表示有一个元素从左侧移出了窗口,窗口大小减一
  • 窗口大小是什么东西?
    • 窗口大小为: R - L(窗口有多大,就存放了多少个元素
    • 当L == R时,表示窗口大小为0,也就是窗口内没有一个元素
  • 什么时候停止?
    • 因为窗口框住的是元素,所以L和R最多只能移动到N-1的位置
      • R指向的位置表示窗口最右边的那个元素
      • L指向的位置表示这个位置的元素过期了
    • 因为我们维护一个大小为K的窗口,所以每次元素入窗口(R++),我们要看看一次当前窗口大小,以及有没有元素需要过期
      • 当 R - L < L时,R继续往后移动
      • 当 R - L == K时,窗口形成了
      • 当 R - L > K时,L需要往后移动,以过期元素
    • 无论如何都是R先到达N - 1的位置的,那么我们需不需要等待L到达N -1的位置才结束呢?
      • 不需要,因为每次元素入参,都会维护窗口的大小(L会改变),当R到达N - 1时,此时只有两种可能:窗口大小 < K,窗口大小 == K;
      • 所以当R到达N-1的位置时,就可以跳出循环了。
  • 注意:L和R都是只往前移动,永远都不会往后退的

双端队列 - 最优解

维护一个滑动窗口并不难,但是我们需要解决一个问题:如何每次用非常小的代价求得滑动窗口的最大值

简单方法是,每次都遍历一遍滑动窗口,但是这样代价就太高了,有没有一种结构让每次求得滑动窗口最大值的代价很小
在这里插入图片描述

我们可以用一个双端队列来实现这样的功能。我们用双端队列存放数组的下标,因为下标既可以得到数组的值,还可以得到值的位置,比单纯的放入数组的值得到的信息更多

在这里插入图片描述
因为我们要求最大值,所以双端队列从队头到队尾要保持下标对应的值是从大到小的。举个例子。

例子一: 模拟数组[3 2 4 6 6]滑动窗口R移动时双端队列的变化

1.R往右移动一个元素,3元素下标从队尾进队列。

在这里插入图片描述
2.R往右移动一个元素,2元素的下标从队尾进入队列。
在这里插入图片描述

3.R再往右移动一个元素,元素4的下标试图从队尾进入,但如果进入将破坏双端队列从队头到队尾从大大小的规则,所以元素2的下标1从队尾出队列(出队列后永远不找回),循环往复直到满足规则时从队尾放入。

在这里插入图片描述
4.R再往右移动一个元素。6比4大,因此4元素对应的下标先从队尾出,6元素的下标从队尾进。
在这里插入图片描述
5.R再往右移动一个元素。6和6相等,但因为要遵循严格单调性,6元素对应的下标3先从队尾出,6元素对应的下标4从队尾进。

在这里插入图片描述

例子二: 模拟数组[6 4 2 5 3]滑动窗口L移动时队列的变化

1.初始状态

在这里插入图片描述
2.L往右移动一个元素,发现0是双端队列头部元素,0从队头弹出。

在这里插入图片描述
于是我们可以知道双端队列移动时的策略:

  • R向右移动时:如果双端队列队尾下标指向元素的值小于要弹入的值,则队尾依次弹出,直到队尾的值大于要弹入的值时,才弹入。(保持双端队列的严格单调性)。

  • L向右移动时:如果要弹出的值就是队头元素,则队头元素弹出;否则双端队列保持不变。

双端队列维持信息的意义是:我们不让R移动,而是让L依次移动的话,谁会依次成为最大值这个信息。

例子三: 模拟数组[1,3,-1,-3,5,3,6,7]滑动窗口L与R如何同时移动的变化

解释过程中队列中都是具体的值,方便理解,具体见代码。
初始状态:L=R=0,队列:{}
i=0,nums[0]=1。队列为空,直接加入。队列:{1}
i=1,nums[1]=3。队尾值为13>1,弹出队尾值,加入3。队列:{3}
i=2,nums[2]=-1。队尾值为3-1<3,直接加入。队列:{3,-1}。此时窗口已经形成,L=0,R=2,result=[3]
i=3,nums[3]=-3。队尾值为-1-3<-1,直接加入。队列:{3,-1,-3}。队首3对应的下标为1,L=1,R=3,有效。result=[3,3]
i=4,nums[4]=5。队尾值为-35>-3,依次弹出后加入。队列:{5}。此时L=2,R=4,有效。result=[3,3,5]
i=5,nums[5]=3。队尾值为53<5,直接加入。队列:{5,3}。此时L=3,R=5,有效。result=[3,3,5,5]
i=6,nums[6]=6。队尾值为36>3,依次弹出后加入。队列:{6}。此时L=4,R=6,有效。result=[3,3,5,5,6]
i=7,nums[7]=7。队尾值为67>6,弹出队尾值后加入。队列:{7}。此时L=5,R=7,有效。result=[3,3,5,5,6,7]


class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int W) {
        int N = nums.size();
        if(W < 1 || N < W){
            return {};
        }
        std::deque<int> qmax;// qmax 窗口最大值的更新结构
         vector<int> ans; //vector<int> ans(N - W + 1);
        int idx = 0;
        for (int R = 0; R < N; ++R) {
            // 维护窗口
            //1. R一定会入队,但是入队之前要把破坏结构的干掉(相等也要将其弹出因为他的小标比你晚过期不需要留你)
            while (!qmax.empty() && nums[qmax.back()] <= nums[R]){
                qmax.pop_back();
            }
            qmax.emplace_back(R); // 单调队列里面存的是数组下标
            // 2.  检测头部数据是否是过期的数据
            if(qmax.front() == R - W){
                qmax.pop_front();
            }
            if (R >= W - 1) {    //此时窗口已经形成
                ans.emplace_back(nums[qmax.front()]);
            }
        }
        return ans;
    }
};

类似题目

题目思路
leetcode:239. 滑动窗口最大值 Sliding Window Maximum
leetcode:155. 栈的最小值 Min Stack
leetcode:159. 至多包含两个不同字符的最长子串 Longest Substring with At Most Two Distinct Characters
[LeetCode] 727. Minimum Window Subsequence 最小窗口序列
leetcode:265. 粉刷房子(K个颜色可选) II
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\],可以使用暴力解法来求解滑动窗口的最大。具体的做法是,遍历数组,对于每个窗口,使用一个内部循环来找到窗口中的最大,并将其存储在结果数组中。时间复杂度为O(n*k),其中n为数组长度,k为窗口大小。 根据引用\[2\],还可以使用队列来求解滑动窗口的最大。具体的做法是,使用一个双端队列来维护一个单调递减的窗口。遍历数组,对于每个元素,首先判断队头是否在滑动窗口范围内,如果不在,则将其从队头移除。然后,将当前元素与队尾元素比较,如果当前元素大于队尾元素,则将队尾元素移除,直到队列为空或者当前元素小于等于队尾元素。最后,将当前元素的索引插入队尾。如果滑动窗口的元素个数达到了k个,并且始终维持在窗口中,就将队头元素加入答案数组中。时间复杂度为O(n),其中n为数组长度。 综上所述,可以使用暴力解法或者使用队列来求解leetcode滑动窗口的最大。 #### 引用[.reference_title] - *1* *3* [leetcode239. 滑动窗口最大](https://blog.csdn.net/kkkkuuga/article/details/124829581)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode#239. 滑动窗口最大 (Java解法)](https://blog.csdn.net/paranior/article/details/114890555)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值