239. 滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
思路
其实问题就是解决怎么在一个窗口内使用O(1)的时间寻找最大值,并且在滑动时,也就是添加一个数和去掉一个数时,也能在线性复杂度找出最大值。
单调队列
可以想到维护一个类似单调栈的数据结构,单调队列,队列内的数据严格单调递减,因此每次寻找最大值时就直接返回队头即可,满足O(1)时间复杂度,然后就是维护单调队列的进队和出队操作了,进队时,从队尾开始,令所有小于当前值的元素全部出队,直到碰到比它大的元素,这样就保证了队列内的数据严格单调递减了。
出队操作对应滑动窗口向前滑动时,需要从队列出一个元素,只需要考虑出的元素是否等于队头元素即可,因为如果不是队头元素,就证明不是最大值,已经被覆盖了。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> d;
int n = nums.size();
vector<int> ans;
for(int i = 0; i < n; i++) {
//先进队k-1个元素
if(i < k - 1) {
//保证单调递减
while(!d.empty() && d.back() < nums[i]) {
d.pop_back();
}
d.push_back(nums[i]);
}
//进队第k个元素
else {
while(!d.empty() && d.back() < nums[i]) {
d.pop_back();
}
d.push_back(nums[i]);
//开始取值
ans.push_back(d.front());
//删除滑动窗口第一个元素
if(d.front() == nums[i - k + 1]) {
d.pop_front();
}
}
}
return ans;
}
};
左右分块
可以将数组按照k分块,定义left_max[i]表示从块头开始到位置i的最大值,状态转移方程为left_max[i] = max(left_max[i - 1], num[i]),特别地,当i % k时,也就是块开始时,left_max[i] = num[i],方向是从左到右,定义right_max[j]表示从右边开始,块头到位置j的最大值,状态转移方程为right_max[j] = max(right_max[j + 1], num[j]),特别地,当j + 1 % k时,right_max[j] = num[j]。
然后,对于[i, i + k - 1]的窗口内,只需要考虑left_max[i - k + 1]和right_max[i]即可。
代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
if(n * k == 0) {
return {};
}
if(k == 1) {
return nums;
}
vector<int> left_max(n);
vector<int> right_max(n);
left_max[0] = nums[0];
right_max[n - 1] = nums[n - 1];
for(int i = 1; i < n; i++) {
if(i % k == 0) {
left_max[i] = nums[i];
}
else {
left_max[i] = max(left_max[i - 1], nums[i]);
}
int j = n - i - 1;
if((j + 1) % k == 0) {
right_max[j] = nums[j];
}
else {
right_max[j] = max(nums[j], right_max[j + 1]);
}
}
vector<int> ans;
for(int i = 0; i < n - k + 1; i++) {
ans.push_back(left_max[i + k - 1] < right_max[i]? right_max[i] : left_max[i + k - 1]);
}
return ans;
}
};