分析
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
很明显,我们可以统计窗口的最大值,然后再滑动的时候,去掉窗口最左侧的值并且加入下一个值后,再得出更新后的最大值。
题解
优先队列
为了统计最大值,我们很容易想到最大堆这种数据结构进行处理,这里使用优先队列priority_queue实现,但是它没办法随着窗口的移动即时删除窗口最左侧的元素,所以这里我们要采用添加元素的同时记录元素的下标,延迟删除元素:
- 堆顶元素下标
<= i - k→代表已经离开窗口,pop丢掉,直到堆顶合法。 - 合法堆顶就是当前窗口最大值。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
priority_queue<pair<int, int>> pq;
int n = nums.size();
vector<int> ans;
for(int i = 0; i < k; i++) pq.emplace(nums[i], i);
ans.push_back(pq.top().first);
for(int i = k; i < n; i++) {
pq.emplace(nums[i], i);
while(pq.top().second <= i - k) pq.pop();
ans.push_back(pq.top().first);
}
return ans;
}
};
时间复杂度:,每个元素入堆一次,出堆最多一次。
单调队列
dq保存数组下标,保证队列里下标对应的数值从队头到队尾严格递减:
- 队头:当前窗口最大值的下标
- 新元素进来,把队列尾部所有 当前值的下标全部踢出去:那些数比当前小,又在当前数左边,永远不可能成为后面窗口的最大值,可以直接丢弃。
举例子:窗口
[3,-1,-3],队列存下标0(3),1(-1),2(-3);遇到新元素 5,-3、-1都比 5 小,全部 pop_back,队列直接变成[4(5)]。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> dq;
vector<int> ans;
int n = nums.size();
for(int i = 0; i < n; i++) {
while(!dq.empty() && nums[i] >= nums[dq.back()]) dq.pop_back();
dq.push_back(i);
while(dq.front() <= i - k) dq.pop_front();
if(i >= k - 1) ans.push_back(nums[dq.front()]);
}
return ans;
}
};
时间复杂度: 空间复杂度:,
deque最多保存窗口内k个小标
分块+预处理
思想来源:把数组按块大小 分块,预处理两块数组:
prefix[i]:从块开头到 i的最大值suffix[i]:从i 到块末尾的最大值
任意窗口 (窗口长度 ),会跨最多两个块:
窗口最大值 =
时间 ,空间 ,不需要单调队列,思维角度不一样。
原理
把数组每 个元素分成一块。
对于窗口
- 如果 和 在同一个块:直接遍历求最大值(极少情况)
- 如果跨两块:
- :L 所在块,从L向右到块结尾的最大值
- :R 所在块,从块开头到 R 的最大值
两者取大,就是整个滑动窗口最大值。
为什么会成立: 窗口被块边界切为两部分:
- 左段:,最大值就是
- 右段:,最大值就是
窗口最大值一定出自这两段其中之一。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> prefix(n);
vector<int> suffix(n);
// prefix[i]:本块内,从块起点到i的最大值
for(int i = 0; i < n; ++i) {
// i是块起点:重新赋值
if(i % k == 0) {
prefix[i] = nums[i];
} else {
prefix[i] = max(prefix[i-1], nums[i]);
}
}
// suffix[i]:本块内,从i到块末尾的最大值
for(int i = n-1; i >= 0; --i) {
// i是块末尾
if(i % k == k-1 || i == n-1) {
suffix[i] = nums[i];
} else {
suffix[i] = max(suffix[i+1], nums[i]);
}
}
vector<int> ans;
// 每个窗口左端点L,右端点 R = L + k -1
for(int L = 0; L <= n - k; ++L) {
int R = L + k - 1;
ans.push_back(max(suffix[L], prefix[R]));
}
return ans;
}
};
时间复杂度:,三次线性遍历 空间复杂度:,两个预处理数组

