单调队列
作用
- 解决滑动窗口最值问题,使得原数组每个元素最多入队和出队1次,就能够求出所有窗口的最值,避免了重复比较求最值
- 优化其他算法
性质
-
以维护滑动窗口最小值为例
-
需要构建单调递增双端队列,使得队首保存当前窗口最小值在原数列中所处的位置
-
遍历原数组
- 若当前值大于队尾值或者队列为空,则直接把当前值的序号插入队尾
- 若当前值小于队尾值所指向原数组的值,则它需要挤到前面去。方法是只要当前队尾所指向的原数组的值比当前值大,就弹出队尾,直到队尾所指向的原数组的值小于当前值或者队列已经空了,再把当前值的序号插到队尾。
-
若遍历到第i个数,窗口大小为k,只要i >= k,则每往后遍历一个数就要打印队首指向原数组的值
-
【注意】有时候队首是不合法的,原因是队首不一定处于当前滑动窗口内部,即队首值必须处于[i-k+1 , i]之间
STL deque 使用
#include<iostream>
#include<deque>
using namespace std;
const int maxn = 1e6+10;
deque<int> q;
int a[maxn];
int main(){
ios::sync_with_stdio(false);
int n,k;
while(cin>>n>>k){
for(int i=1; i<=n; i++) cin>>a[i];
//最小值 维护单调增队列
for(int i=1; i<=n; i++){
//遇到更小的就往前挤
while(!q.empty() && a[q.back()] > a[i] ) q.pop_back();
q.push_back(i);
//删除头 保证front的位置处于滑动窗口内 即 [i-k+1 , i]
//这种情况只可能在 i>=k 时才需要考虑
if(i >= k){
while(!q.empty() && q.front() <= i-k) q.pop_front();
//打印最小值
cout<<a[q.front()]<<" ";
}
}
cout<<endl;
//q.erase(q.begin(),q.end());
while(!q.empty()) q.pop_back();
//最大值 维护单调减队列
for(int i=1; i<=n; i++){
//遇到大的就往前挤
while(!q.empty() && a[q.back()] < a[i] ) q.pop_back();
q.push_back(i);
//删除头 保证front的位置处于滑动窗口内 即 [i-k+1 , i]
//这种情况只可能在 i>=k 时才需要考虑
if(i >= k){
while(!q.empty() && q.front() <= i-k) q.pop_front();
//打印最大值
cout<<a[q.front()]<<" ";
}
}
cout<<endl;
}
}
数组模拟队列
#include<iostream>
#include<deque>
using namespace std;
const int maxn = 1e6+10;
int a[maxn];
int b[maxn];
int main(){
ios::sync_with_stdio(false);
int n,k;
while(cin>>n>>k){
int p=1,q=1;
for(int i=1; i<=n; i++) cin>>a[i];
//最小值 维护单调增队列
for(int i=1; i<=n; i++){
//遇到更小的就往前挤
while(p < q && a[b[q-1]] > a[i] ) q--;
b[q++] = i;
//删除头 保证front的位置处于滑动窗口内 即 [i-k+1 , i]
//这种情况只可能在 i>=k 时才需要考虑
if(i >= k){
while(p < q && b[p] <= i-k) p++;
//打印最小值
cout<<a[b[p]]<<" ";
}
}
cout<<endl;
//q.erase(q.begin(),q.end());
p = 1 , q = 1;
//最大值 维护单调减队列
for(int i=1; i<=n; i++){
//遇到大的就往前挤
while(p < q && a[b[q-1]] < a[i] ) q--;
b[q++] = i;
//删除头 保证front的位置处于滑动窗口内 即 [i-k+1 , i]
//这种情况只可能在 i>=k 时才需要考虑
if(i >= k){
while(p<q && b[p] <= i-k) p++;
//打印最大值
cout<<a[b[p]]<<" ";
}
}
cout<<endl;
}
}