题目描述
给定一个数组 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
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
思路
这是一道典型的单调队列的模板题。单调队列具有这样的性质:当有新元素入队或出队时,队列元素之间的单调性不发生变化。我们只需要维护一个单调递减的队列,随着滑动窗口遍历过整个数组,若新元素会破坏这种单调性,即队尾元素小于等于新元素时,将队尾元素出队;循环执行这个判断直至新元素入队时队列的单调性不变。
while(队列不空 && 队尾元素 <= 新元素){
队尾元素出队;
}
新元素入队;
举例说明: 假设有单调队列为:{9,7,5,4,3};滑动窗口新加入的元素为6,那么执行上述循环判断:
- 队列不空 && 3 < 6, 3出队,单调队列变为{9,7,5,4};
- 队列不空 && 4 < 6, 4出队,单调队列变为{9,7,5};
- 队列不空 && 5 < 6, 3出队,单调队列变为{9,7};
- 队列不空 && 7 > 6, 6入队,单调队列变为{9,7,6}; 这样队首元素始终为当前滑动窗口区间的最大值。
细心的同学可能会发现存在一个问题:滑动窗口会不断地向右移动,原本的最大元素可能会从区间的左侧“掉”出区间。那么我们该如何解决这个问题呢?
很简单,我们使用数组模拟单调队列, 在模拟的单调队列中我们只存储原数组数字的下标,而不存储原数字。
这样做的好处在于我们可以在滑动窗口遍历时,在每次循环的入口处判断单调队列的队首元素的下标是不是已经掉出滑动窗口了。
q[head] = 队首元素下标, q[tail] = 队尾元素下标;
while(...){
if(head <= tail && q[head] < i - k + 1)//i为滑动窗口最右端对应的下标,k为滑动窗口的长度
{
head ++ ;
}
...
}
通过这样的方式,我们就可以保证在遍历原数组的过程中,模拟的单调队列的队首元素始终是当前滑动窗口的最大值。
C++代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
//单调队列
int len = nums.size();
if(len == 0)//边界判断
return {};
int q[len + 1];//我们用数组模拟单调队列,本题中单调队列并不是真实存在的数据结构
//而是一个逻辑结构
vector<int> res;//res是我们存储每个滑动窗口中最大值的vector
int head = 0, tail = -1;//head和tail是q的索引
//注意q[head]存储的是队列头元素的下标,nums[q[head]才是队列头元素
for(int i = 0; i < len; i ++ ){
if(head <= tail && i - k + 1 > q[head])//当单调队列的队首元素掉出滑动窗口区间时
//head <= tail这个判定条件相当于 !队列.empty()
head ++ ;
while(head <= tail && nums[q[tail]] <= nums[i])//当新元素的加入会破坏单调队列的单调性时
tail -- ;
q[ ++ tail] = i;//逻辑上:新元素入队;实际上:在数组q的末尾存储新元素的下标
if(i - k + 1 >= 0)//只有当滑动窗口中元素数量 == k时
res.push_back(nums[q[head]]);
}
return res;
}
};
本文正在参与「掘金 3 月闯关活动」,点击查看活动详情。