例题.滑动窗口
有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
一、单调队列特点
1.队列内元素值单调,递增或递减;
2.队头为窗口内元素的最值,队尾为窗口的尾元素;
3.除了队头可以进行出队操作外,队尾同样可以进行出队操作
二、原理
每次新加入一个元素进行判断时,会在队列中删除肯定不满足条件的值,从而省去了比较。
1.例子(输出窗口最小值):
比如在原来的窗口[ 1, 3 , -1 ]中加入-3时,可以发现在所有包含-3的窗口中,那些比-3大的元素值都不会是我们的选择,那么我们不妨直接把这些数从队列中移除;
我们会将答案从队头输出,为了便于理解这些无用值会从队尾移出。
2.模拟(输出窗口最大值)
3.队头何时出队
在队列中存入下标,若发现队头的下标与当前元素下标的差值大于等于窗口长度,则进行出队操作
三、代码实现及注释
#include<bits/stdc++.h>
using namespace std;
const int N=1000001;
int p[N],q[N],a[N];
int n,m;
void MAX(){
int h=0,t=-1;
for(int i=1;i<=n;i++){
if(h<=t&&p[h]<=i-m)h++;//删除已经被输出的队头元素,处于“窗外”
while(h<=t&&a[i]>a[p[t]])t--;//输出队列中肯定不会是结果的值(特点1)
p[++t]=i;//向队尾存入窗口为元素下标(特点2)
if(i>=m)cout<<a[p[h]]<<" ";//(特点2)
}
}
void MIN(){
int h=0,t=-1;
for(int i=1;i<=n;i++){
if(h<=t&&q[h]<=i-m)h++;
while(h<=t&&a[i]<a[q[t]])t--;
q[++t]=i;
if(i>=m)cout<<a[q[h]]<<" ";
}
cout<<endl;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
MIN();
MAX();
return 0;
}
STL——双向队列写法
#include<bits/stdc++.h>
using namespace std;
deque<int>q;//min
deque<int>p;//max
int n,m;
int a[1000001];
void MIN(){
for(int i=1;i<=n;i++){
if(!p.empty()&&p.front()<=i-m)p.pop_front();
while(!p.empty()&&a[i]<a[p.back()])p.pop_back();
p.push_back(i);
if(i>=m)cout<<a[p.front()]<<" ";
}
cout<<endl;
}
void MAX(){
for(int i=1;i<=n;i++){
if(!q.empty()&&q.front()<=i-m)q.pop_front();
while(!q.empty()&&a[i]>a[q.back()])q.pop_back();
q.push_back(i);
if(i>=m)cout<<a[q.front()]<<" ";
}
cout<<endl;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
MIN();
MAX();
return 0;
}