题目
给你输入一个数组nums
和一个正整数k
,有一个大小为k
的窗口在nums
上从左至右滑动,请你输出每次窗口中k
个元素的最大值。
分析
这道题难点在于如何在O(1)
时间动态算出每个「窗口」中的最大值,使得整个算法在线性时间完成。
如果减少的这个数恰好是窗口中的最值,就需要遍历所有数重新找新的最值。
想在 O(1) 的时间得出新的最值,需要「单调队列」这种特殊的数据结构来辅助。
单调队列实现
就是一个「队列」,只是使用了一点巧妙的方法,使得队列中的元素全都是单调递增(或递减)的。
#include <deque>
#include <iostream>
using namespace std;
// 必须使用一种数据结构支持在头部和尾部进行插入和删除,很明显 deque 和 list 符合要求。
class MonotonicQueue {
private:
deque<int> d;
public:
MonotonicQueue() {}
void push(int n);
void pop(int n);
int max();
};
void MonotonicQueue::push(int n) {
// 要把前面比自己小的元素都删掉
// 如果每个元素被加入时都执行了该操作,最终单调队列中的元素大小就会保持一个单调递减的顺序
while (!d.empty() && d.back() < n) {
d.pop_back();
}
// 将n加入尾部
d.push_back(n);
}
void MonotonicQueue::pop(int n) {
// 想删除的队头元素n可能已经被「压扁」了,已经不存在了,所以这时候就不用删除了
if (n == d.front()) {
d.pop_front();
}
}
int MonotonicQueue::max() { return d.front(); }
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MonotonicQueue w;
vector<int> res;
for (int i = 0; i < nums.size(); i++) {
// 先填满窗口的前k-1个元素
if (i < k - 1) {
w.push(nums[i]);
} else {
w.push(nums[i]);
res.push_back(w.max());
w.pop(nums[i - k + 1]);
}
}
return res;
}
总结
单独看push
操作的复杂度确实不是O(1)
,但是算法整体的复杂度依然是O(N)
线性时间。要这样想,nums
中的每个元素最多被push_back
和pop_back
一次,没有任何多余操作,所以整体的复杂度还是O(N)
。
空间复杂度就很简单了,就是窗口的大小O(k)
。