题目地址:https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/
题目描述
给定一个数组 nums
和滑动窗口的大小 k
,请找出所有滑动窗口里的最大值。
题目示例
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
解题思路
暴力法:扫描每个滑动窗口的所有数字并找出其中的最大值,即直接移动滑动窗口,每次统计滑动窗口中的最大值,并将其放入到数组arr中即可。需要注意的是遍历循环的上限是nums.size()-k+1,因为滑动窗口如果移动到此位置时,刚好将整个数组遍历完毕。
动态规划:对暴力法进行优化可得到动态规划方法。
双端队列:本题的关键在于如何从滑动窗口中动态获取每一个最大值,定义队列始终保证队列中的元素均处在当前滑动窗口范围内且元素由队头至队尾呈单调递减的状态,只把有可能成为滑动窗口最大值的数值存入双端队列deque。定义一个双端队列dq,用来保存有可能成为滑动窗口最大值的数字的下标,便于从队尾删除不可能为滑动窗口最大值的候选元素,另外滑动窗口的最大值总是出现在队首,在存入一个数字的下标之前,首先判断队列里已有数字是否小于待存入的数字。如果小于,那么这些数字已经不可能是滑动窗口的最大值,将他们依次从队尾删除。同时,如果队头的数字已经从窗口里滑出,那么滑出的数字也需要从队头删除。具体步骤如下:
- 队尾插入当前元素;
- 队头弹出最大值,原因在于单调队列的队头元素一定是当前滑动窗口的最大值;
- 队尾弹出元素,原因在于为维持单调队列的单调性(递减),要先判断待插入元素与队尾元素的大小关系,如果待插入元素大于队尾元素,说明队尾元素肯定不会是滑动窗口的最大值,所以要先将队尾元素弹出再将插入元素进队;
- 队头弹出已经不属于当前滑动窗口范围的元素。为什么会从队头弹出而不会从队尾弹出不属于窗口的元素?原因在于上一个队尾弹出操作中,为了维持单调性,不可能成为最大值的元素早已经被弹出,所以说剩下可能需要弹出的元素只有队头的窗口最大元素。同时也可以保证最大值的实时更新,如果没有这一步操作,当遇到整个数组的最大值时,滑动窗口的最大值肯定不会再变化(甚至不用等到那时候,超出窗口大小的一定范围内的数组最大值也会导致滑动窗口最大值在那一段时间不变)。
程序源码
暴力法
class Solution { public: vector<int> maxSlidingWindow(vector<int>& nums, int k) { if(nums.size() == 0) return nums; //nums.empty() int len = nums.size() - k + 1; //输出数组长度大小 vector<int> arr; for(int i = 0; i < len; i++) { int max_num = nums[i]; for(int j = i + 1; j < i + k; j++) { max_num = max(nums[j], max_num); } arr.push_back(max_num); } return arr; } };
动态规划
class Solution { public: vector<int> maxSlidingWindow(vector<int>& nums, int k) { if(nums.size() == 0) return nums; vector<int> dp(nums.size() - k + 1, INT_MIN); for(int i = 0; i < k; i++) { dp[0] = max(dp[0], nums[i]); } for(int i = 1; i < dp.size(); i++) { if(nums[i - 1] < dp[i - 1]) { dp[i] = max(dp[i - 1], nums[i + k - 1]); //转入下一个滑动窗口,即比较dp[0]和nums[3] } else //比如dp[1]=3与nums[1]=3 { for(int j = i; j < i + k; j++) { dp[i] = max(dp[i], nums[j]); //比如滑动窗口[-1,-3,5]与dp[2] } } } return dp; } };
双端队列
class Solution { public: vector<int> maxSlidingWindow(vector<int>& nums, int k) { if(nums.size() == 0) return nums; vector<int> arr; deque<int> dq; for(int i = 0; i < nums.size(); i++) { while(!dq.empty() && nums[i] > nums[dq.back()]) dq.pop_back(); if(!dq.empty() && dq.front() <= (i - k)) dq.pop_front(); dq.push_back(i); if(i >= k - 1) arr.push_back(nums[dq.front()]); } return arr; /* vector<int> arr; deque<int> dq; //保存有可能成为滑动窗口最大值的数字的下标 for(int i = 0; i < nums.size(); i++){ if(i < k){ while(!dq.empty() && nums[dq.back()] < nums[i]) dq.pop_back(); //如果队列不为空,且队尾元素小于数组元素,说明队尾元素肯定不是滑动窗口的最大值,因此从队尾弹出,因为双端队列只存储滑动窗口最大值 dq.push_back(i); //填充滑动窗口 } else{ //移动滑动窗口 arr.push_back(nums[dq.front()]); //从队头获取最大值放入arr数组中 while(!dq.empty() && nums[dq.back()] < nums[i]) dq.pop_back(); if(!dq.empty() && dq.front() <= (i - k)) dq.pop_front(); //如果队列不为空,且当前队头的最大值已不在当前滑动窗口范围内,从队头弹出 dq.push_back(i); } } arr.push_back(nums[dq.front()]); return arr;*/ } };
参考文章