有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
以求最小值为例
f[i]表示以i结尾的窗口中的最小值
f[i]=min(a[j]),i-k+1<=j<=i
暴力算法O(n^2)
for:i=1~n
for:j=i-k+1~i
f[i]=min(a[j)
单调队列:队尾进队出队,队头出队(维护子序列的单调性)
5 7 8 ——> 5 7 ——> 5 ——> 5 6 ——> 5 6 8
1.队尾出队的条件:队列不空且新元素更优,队中旧元素队尾出队
2.每个元素必然从队尾进队一次
3.队头出队的条件:队头元素滑出了窗口
注意:队列中存储元素的下标,方便判断队头出队
//维护窗口最小值
int h=1,t=0; //清空队列
for(int i=1;i<=n;i++) { //枚举序列
while(h<=t&&a[q[t]]>=a[i] t--; //队尾出队(队列不空且新元素更优) min(a[i])
q[++t]=i; //队尾入队(存储下标 方便判断队头出队) j<=i
if(q[h]<i-k+1) h++; //队头出队(队头元素滑出窗口) i-k+1<=j
if(i>=k) cout<<a[q[h]]<<" "; //使用最值
}
每个元素最多进队和出队各一次,时间复杂度为O(n)
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N],q[N];
int main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int n,k;cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
//维护窗口最小值
int h=1,t=0;
for(int i=1;i<=n;i++) {
while(h<=t&&a[q[t]]>=a[i]) t--;
q[++t]=i;
if(q[h]<i-k+1) h++;
if(i>=k) cout<<a[q[h]]<<" ";
}
cout<<'\n';
//维护窗口最大值
h=1,t=0;
for(int i=1;i<=n;i++) {
while(h<=t&&a[q[t]]<=a[i]) t--;
q[++t]=i;
if(q[h]<i-k+1) h++;
if(i>=k) cout<<a[q[h]]<<" ";
}
return 0;
}
维护最小值就是维护窗口内的升序子序列
维护最大值就是维护窗口内的降序子序列
标签:队尾,窗口,队列,int,最小值,出队,最值,模板 From: https://blog.csdn.net/2301_80405485/article/details/136918740