leetcode 239.滑动窗口最大值
题干
给你一个整数数组 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]
示例 3:
输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:
输入:nums = [9,11], k = 2
输出:[11]
示例 5:
输入:nums = [4,-2], k = 2
输出:[4]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
题解
首先想到用deque直接模拟滑动窗口前进后出的操作(实际上queue不就行了吗,上了标签的当),然后用max_element寻找窗口内最大值,直接模拟显然数据量大的时候超时。
于是改进,记录移动窗口前窗口内最大值的下标和值,若移动后该最大值仍在窗口内,则只需要比较其与新进入窗口的值即可,若已经不在窗口内,则重新检查最大值。
显然这样的写法在最大值处在窗口左侧的情况下时间复杂度仍然很高,于是在第49个样例还是超时了
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
//记录移动前窗口的下标与最大值
pair<int,int> preMax = {0,0};
deque<int> window;
vector<int> ans;
//将nums的前k个元素全部加入窗口
for(int i = 0 ; i < k ; ++i){
window.push_back(nums[i]);
}
preMax = {max_element(window.begin(),window.end()) - window.begin() , *max_element(window.begin(),window.end()) };
//cout<<preMax.first<<' '<<preMax.second;
ans.push_back(preMax.second);
//开始移动窗口
for(int i = k ; i < n ; ++i){
//计算移动后窗口第一位的下标
int windowHead = i - k + 1;
window.pop_front();
window.push_back(nums[i]);
if(windowHead > preMax.first){
//如果之前的最大值已经被抛出窗口外
//更新preMax的值
preMax = {max_element(window.begin(),window.end()) - window.begin() , *max_element(window.begin(),window.end()) };
ans.push_back(preMax.second);
}else{
//如果之前的最大值仍然在窗口内
if(nums[i] > preMax.second){
//新进入窗口的数更大
//更新premax的值
preMax.first = i;
preMax.second = nums[i];
ans.push_back(nums[i]);
}else{
ans.push_back(preMax.second);
}
}
}
return ans;
}
};
使用优先队列模拟窗口,使用和上述类似的方式,由于优先队列的top元素一定是值最大的,所以要检查最大元素的下标是否在窗口内,若不在则将其弹出优先队列
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
//用优先队列模拟窗口
priority_queue<pair<int,int> > window;
vector<int> ans;
//将nums的前k个元素全部加入窗口
for(int i = 0 ; i < k ; ++i){
window.push({nums[i],i});
}
ans.push_back(window.top().first);
//开始移动窗口
for(int i = k ; i < n ; ++i){
//计算当前窗口最左下标
int windowHead = i - k + 1;
window.push({nums[i],i});
while(window.top().second < windowHead){
window.pop();
}
ans.push_back(window.top().first);
}
return ans;
}
};
用双端队列保存元素对应下标的方法,在窗口内部总是左侧元素被先弹出,因此只要较大的元素在右侧,那最大值就一定与左侧元素无关,换言之,上升序列不影响最大值的考量,我们需要考虑的是下降队列
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
//用双端队列模拟窗口,但只储存下标,且下标对应的值按照其在队列中的顺序递减
deque<int> window;
vector<int> ans;
//将nums中元素对应下标加入队列,当加入的下标对应元素大于队尾时,将队尾元素不断弹出至加入下标对应元素小于队尾元素,以保证队列中下标对应的值严格非增
for(int i = 0 ; i < k ; ++i){
while(!window.empty() && nums[i] >= nums[window.back()]){
window.pop_back();
}
window.push_back(i);
}
ans.push_back(nums[window.front()]);
//开始移动窗口
for(int i = k ; i < n ; ++i){
int windowHead = i - k + 1;
//加入新元素时按照之前的策略弹出小于加入下标对应元素的队尾下标
while(!window.empty() && nums[i] >= nums[window.back()]){
window.pop_back();
}
window.push_back(i);
//弹出队首已不在窗口内的下标,保证最大元素仍在窗口内
while(window.front() < windowHead){
window.pop_front();
}
ans.push_back(nums[window.front()]);
}
return ans;
}
};