原题见力扣:https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/
这里讲的滑动窗口并不是计网TCP中的滑动窗口. 不过移动的方式其实差不多,就是在一串数据上以固定大小的窗口滑动,每次都在该窗口内寻找最大值. 这与深度学习中的max_pooling1D思想神似. (stride=1, kernel=k)
这题用暴力解法当然十分简单,时间复杂度为O(kN)
,但本题的要领并不在此.
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
if (nums.size() == 0) {
return vector<int>();
}
vector<int> rec;
int len = nums.size();
for (int i = 0; i <= len-k; ++i) {
int maxx = 0x80000000;
for (int j = i; j < i+k; ++j) {
maxx = max(maxx, nums[j]);
}
rec.push_back(maxx);
}
return rec;
}
};
本题蕴含的一个知识点是单调队列,通过单调队列可以将求滑窗内最大值的时间复杂度从O(k)
降为O(1)
. 下面学习一下单调队列.
单调队列是一种特殊的队列,队列中的元素按照从队头到队尾递增或递减的顺序排列 (并不严格递增或严格递减). 这样,就有可能在常数的时间内取得到队列的最大值.
结合滑动窗口,这样的队列应该如何设计?我们结合一个例子来看. (k=3)
扫描第1个元素,直接入队
扫描第2个元素,入队并更新队列
扫描第3个元素,入队.
现在已经形成了第1个滑动窗口,直接取队头输出,序列为3
接着扫描到-3,更新队列,取队头输出,序列为3 3
.
最后,扫描到5,原队中3应该先出队,5进队后更新队列.
取队头输出,序列为3 3 5.
这样的单调队列,满足:
deque
内仅包含窗口内的元素 (那么每轮滑动移除了元素nums[i - 1]
,需将deque
内的对应元素一起删除)deque
内的元素非严格递减 (每轮滑动添加了元素nums[j+1]
,需将deque
内所有<nums[j+1]
的元素删除)
算法流程:
- 定义 双端队列
deque
,结果列表res
,数组长度n
; - 滑动窗口线性算法:(左边界范围
i∈[1−k,n+1−k]
,右边界范围j∈[0,n−1]
)- 若
i>0
且 队首元素deque[0] == 被删除元素 nums[i−1]
:则队首元素出队; - 删除
deque
内所有<nums[j]
的元素,以保持deque
递减; - 将
nums[j]
添加至deque
尾部; - 若已形成窗口(即
i≥0
):将窗口最大值(即队首)添加至列表res
.
- 若
- 返回结果列表
res
。
复杂度:
- 时间复杂度:每个元素顶多入队1次,出队1次,算法为线性时间复杂度
- 空间复杂度:线性
参考代码:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
// nums为空或返回值就是原数组的情况
if (k == 0 || k == 1) {
return nums;
}
vector<int> rec;
int len = nums.size();
deque<int> dq;
dq.push_back(nums[0]);
for (int i = 1; i < len; ++i) {
// 是否被移出窗口
if (i-k >= 0 && nums[i-k] == dq.front()) {
dq.pop_front();
}
// 窗口保持递减
while (!dq.empty() && dq.front() < nums[i]) {
dq.pop_front();
}
while (!dq.empty() && dq.back() < nums[i]) {
dq.pop_back();
}
dq.push_back(nums[i]);
// 形成窗口,开始存放数据
if (i >= k-1) {
rec.push_back(dq.front());
}
}
return rec;
}
};