题目描述
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值 。
示例 1:
输入: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
示例 2:
输入:nums = [1], k = 1
输出:[1]
提示:
1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
1 <= k <= nums.length
思路
一种错误的思路:考虑使用一个大顶堆来存放窗口里的k个数字,方便知道最大值,但是窗口是移动的,大顶堆无法移除数值,所以不能用。
正确的思路:
使用单调队列来存放窗口里的数字,随着窗口的移动,队列能够进出元素,并更新最大值。队列里的值一定要排序,且最大值要放在出队口。
在设计单调队列的时候,pop,和push操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
保持如上规则,每次窗口移动的时候,只要deque.front()就可以返回当前窗口的最大值。
代码
C++版:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// 使用双端队列而不是普通队列,方便实现单调队列
vector<int> result;
deque<int> d;
// 先将前k的元素放进队列
for(int i=0;i<k;i++){
// 保证队列前大后小
while(!d.empty() && nums[i]>d.back()){
d.pop_back();
}
d.push_back(nums[i]);
}
result.push_back(d.front());
// 处理剩余元素
for(int j=k;j<nums.size();j++){
// 注意执行顺序
// 1.移除队首
if(!d.empty() && nums[j-k]==d.front()){
d.pop_front();
}
// 2.队列末尾插入元素,同时更新最大值
while(!d.empty() && nums[j]>d.back()){
d.pop_back();
}
d.push_back(nums[j]);
// 记录对应的最大值
result.push_back(d.front());
}
return result;
}
};
Python版:
from collections import deque
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
d = deque() # 需要使用deque实现单调队列,直接使用list会超时
result = []
for i in range(k): # 先将前k的元素放进队列
while d and nums[i]>d[-1]:
d.pop()
d.append(nums[i])
result.append(d[0]) # result记录前k的元素的最大值
for j in range(k, len(nums)):
if d and nums[j-k]==d[0]:
d.popleft()
while d and nums[j]>d[-1]:
d.pop()
d.append(nums[j])
result.append(d[0])
return result
需要注意的地方
1.本题的单调队列不需要维护滑动窗口中的所有元素,而是维护有可能成为窗口里最大值的元素就可以了,即保证之前的元素一定不比新加入的元素小,小就弹出,那些小的就是没有必要维护的元素。
2.单调队列的实现并不固定,且可以单独抽象成一个类,总之要保证队列里的元素单调递增或递减。