滑动窗口 (单调队列 ) (单调栈)

滑动窗口

题目描述:
给一个长度为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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值