滑动窗口
题目描述:
给一个长度为N的数组,一个长为K的滑动窗体从最左端移至最右端,你只能看到窗口中的K个数,每次窗体向右移动一位。你的任务是找出窗体在各个位置时窗内元素的最大值和最小值。
备注: 对于100%的数据,K ≤ N ≤ 10^6
思路: 这是很经典的单调队列的板子题,最常应用的情景就是;
Code1:
通过维护双端队列(deque)的单调性得到单调队列(不常用)
#include<iostream>
#include<algorithm>
#include<deque>
using namespace std;
const int N = 2e6+7;
deque<int> Q1 , Q2;
int a[N];
int n,k;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){ //维护单调不减队列(从队头依次递增或相等)
scanf("%d",&a[i]);
while(!Q1.empty() && a[i] < a[Q1.back()] ) Q1.pop_back(); //若队尾大于下一个元素,则弹出
Q1.push_back(i); // 使用下标
while(!Q1.empty() && Q1.front() < i-k+1) Q1.pop_front(); //判断队头是否已经划出窗口
if(i>=k) printf("%d ",a[Q1.front()]); // 队头是最小的元素的下标;
}
printf("\n");
for(int i=1;i<=n;i++) { //维护单调不增队列(从队首依次递减或相等)
while(!Q2.empty() && a[i] > a[Q2.back()] ) Q2.pop_back(); //若队尾小于下一个元素,则弹出
Q2.push_back(i);
while(!Q2.empty() && Q2.front() < i-k+1) Q2.pop_front();
if(i>=k) printf("%d ",a[Q2.front()]); //队首是最大的元素的下标;
}printf("\n");
return 0;
}
Code2:
通过使用数组模拟双端队列得到单调队列;
#include<iostream>
#include<algorithm>
#include<deque>
using namespace std;
const int N = 2e6+7;
int a[N],q[N];
int n,k,f,t;
int main()
{
scanf("%d%d",&n,&k);
f=1,t=0; // f为队头,t为队尾(数组下标小的那端为队首)
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
while(a[q[t]] > a[i] && f<=t) t--;
while(q[f] < i-k+1 && f<=t) f++;
q[++t] = i;
if(i>=k) printf("%d ",a[q[f]]);
}printf("\n");
f=1,t=0;
for(int i=1;i<=n;i++){
while(a[q[t]] < a[i] && f<=t) t--;
while(q[f] < i-k+1 && f<=t) f++;
q[++t] = i;
if(i>=k) printf("%d ",a[q[f]]);
}printf("\n");
return 0;
}
Code3:
使用线段树维护区间最大最小值,由于题目卡内存,GG ~了,只过了70%
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e6+7;
struct node2{
int maxx,minn;
};
int w[N];
struct node{ //树节点结构,节点编号为下标,默认u<<1 为左子树,u<<1|1为右子树
int l, r;
node2 data;
}tr[4*N]; // 叶子节点(N)+ 其他父节点 (3N)
void pushup(int u) //更新节点信息
{
//tr[u].sum = tr[u<<1].sum +tr[u<<1|1].sum;
node2 t1 = tr[u<<1].data , t2 = tr[u<<1|1].data;
tr[u].data.minn = min(t1.minn,t2.minn);
tr[u].data.maxx = max(t1.maxx,t2.maxx);
}
void build(int u,int l,int r) //建树
{
if(l==r) tr[u]={l,r,{w[l],w[l]} }; // 叶子节点赋初始值
else {
tr[u] = {l,r}; //初始值
int mid = l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r); //构建左右子树
pushup(u); //叶子节点变化,更新父节点信息
}
}
node2 query(int u,int l,int r) //查询函数,这里是最大最小值
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].data ; //叶子节点
int mid = tr[u].l + tr[u].r >> 1;
node2 k;
k.maxx =-2e9 ;
k.minn = 2e9 ; //维护左右子树
if(l <= mid) {
node2 t = query(u<<1, l,r);
k.maxx = max(k.maxx,t.maxx) , k.minn = min(k.minn,t.minn);
}
if(r>=mid+1) {
node2 t = query(u<<1|1,l,r);
k.maxx = max(k.maxx,t.maxx) , k.minn = min(k.minn,t.minn);
}
return k;
}
int ans[N];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&w[i]); //读入叶子节点权值
build(1,1,n); //建树
for(int i=1;i+k-1<=n;i++){
node2 t = query(1,i,i+k-1 );
printf("%d ",t.minn);
ans[i] = t.maxx;
}printf("\n");
for(int i=1;i+k-1<=n;i++){
printf("%d ",ans[i]);
}printf("\n");
return 0;
}
AcWing 830. 单调栈
应用情景:O(n) 维护某个元素离它最近的 (左边或右边) 小于 (或大于) 它的元素。
Code:
维护元素左边离它最近的小于它的值:
#include<iostream>
using namespace std;
const int N = 1e5+7;
int a[N];
int stk[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int tt=0;
for(int i=1;i<=n;i++){
while(tt&&stk[tt]>=a[i]) tt--;
if(!tt) printf("-1 "); //栈空,不存在
else printf("%d ",stk[tt]);
stk[++tt] = a[i];
}
return 0;
}