239.滑动窗口最大值

🛴 239.滑动窗口最大值

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

返回 滑动窗口中的最大值

思路

基本思想

本题求的是一个窗口中的最大值,并且窗口元素还会滑动,每滑动一次都需要重新计算窗口中元素的最大值,从而将最终得到的结果存放到一个容器中,最先想到的办法就是依次遍历,左侧出一个元素,右侧进一个元素,每形成一个新的滑动窗口,都需要遍历一遍滑动窗口中的元素找到最大值,耗时的步骤就是在这里

为了减小时间开销,定义一个双端队列,并且保持对头元素一直是当前窗口中的最大值,为了保持这个性质,对头的元素一定是当前窗口中最大的,因为对于一个窗口来说,我们只关注最大值,所以比最大值小的元素都没有用,在遍历的过程中就可以舍弃,保持双端队列的递减即可

每当元素入队列时,都需要保持这个递减的关系(非递增),当当前元素等于当前对头元素,说明当前窗口需要滑动,最大值需要更新,而双端队列中存储的递减元素就是每一个窗口中递减排序的元素,删除对头元素之后,下一个滑动窗口的最大值还是在新队头

一定要理解上面这段话,使用的是双端队列来解题

当当前元素等于对头元素时,说明当前窗口已满,对头元素不再是当前窗口中的最大值,此时对头元素需要删除,但是由于对头元素后面的元素都尽可能的保持最大,所以对头元素出对后,新的对头元素还是当前窗口的最大值

最终形成的代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
    //不是求滑动窗口中的和最大值,而是求每一个滑动窗口中的元素最大值
    //暴力法是针对每一个窗口都从头筛选一遍
    //保持对头始终是最大的元素
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        //判断极端情况
        if(nums.size()<k||k==0)
            return res;
        //先累计一个滑动窗口,之后进一个出一个,每次都判断是不是最大
        deque<int> windows;
        int max=0;
        int start=0;
        //先累计一个窗口,保持对头元素最大
        for(int i=0;i<k;++i){
            while(!windows.empty()&&nums[i]>windows.back())
                windows.pop_back();
            windows.push_back(nums[i]);
        }
        //这是第一个窗口
        res.push_back(windows.front());
        //后面的元素也需要保持对头元素最大
        for(int i=k;i<nums.size();++i){
            //当前窗口元素已满,窗口的最大值元素只能在这里删除
            if(windows.front()==nums[i-k])
                windows.pop_front();
            //保持对头元素最大,这里不能是等于,一旦当前窗口中出现两个相同的最大值
            //前一个最大值就保不住了
            //相当于windows的front保存的是最近几个窗口的最大值
            while(!windows.empty()&&nums[i]>windows.back())
                windows.pop_back();
            windows.push_back(nums[i]);
            res.push_back(windows.front());
        }
        return res;
    }
};

这段代码将形成一个完整的窗口前和一个完整的窗口后,形成一个完整的窗口前,这样进一步区分元素入队的判断条件,减小判断时的消耗,如果不将形成窗口之前和之后分开,那么就需要在统计结果时和对头元素出对时进行进一步的判断

执行流程

  1. 不停地入队元素,保持队列的非递增,也就是对头元素始终是最大值
  2. 当当前元素与对头元素相同,相当于当前窗口元素已满,需要删除元素
  3. 当形成一个完整的窗口之后,就可以统计结果
  4. 返回最终的结果

代码

根据以上分析,得出以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
    //不是求滑动窗口中的和最大值,而是求每一个滑动窗口中的元素最大值
    //暴力法是针对每一个窗口都从头筛选一遍
    //保持对头始终是最大的元素
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        //判断极端情况
        if(nums.size()<k||k==0)
            return res;
        //先累计一个滑动窗口,之后进一个出一个,每次都判断是不是最大
        deque<int> windows;
        //始终保持对头元素最大
        for(int i=0;i<nums.size();++i){
            //当前窗口元素已满,窗口的最大值元素只能在这里删除
            if(i>=k&&windows.front()==nums[i-k])
                windows.pop_front();
            //保持对头元素最大,这里不能是等于,一旦当前窗口中出现两个相同的最大值
            //前一个最大值就保不住了
            //相当于windows的front保存的是最近几个窗口的最大值
            while(!windows.empty()&&nums[i]>windows.back())
                windows.pop_back();
            windows.push_back(nums[i]);
            if(i>=k-1)
                res.push_back(windows.front());
        }
        return res;
    }
};

总结

核心就是始终保持对头元素最大,只有当当前对头元素超出窗口范围才删除