单调栈与单调队列
单调栈
定义:栈内元素单调按照递增(递减)顺序排列的栈
基本作用:可以从数组中找到左右两边比x大(小)的数,时间复杂度为O(n)
单调栈的基本操作:
●为了维护栈的单调性,在进栈过程中需要进行判断,具体进栈过程如下:假设当前进栈元素为e,
●对于单调递减栈,从栈顶开始遍历元素,把小于e或者等于e的元素弹出栈,直至遇见一个大于e的元素或者栈为空为止,然后再把e压入栈中,这样就能满足从栈底到栈顶的元素是递减的。
●对于单调递增栈,则每次弹出的是大于e或者等于e的元素,直至遇见一个小于e的元素或者栈为空为止。

代码模板
a[n+1]=2e+9; for (int i = 1; i <= n; i ++ ) { while (tt && a[s[tt]]>=a[i]) tt -- ; stk[ ++ tt] = i; }
模板题
洛谷题库P5788[单调栈]
#include <iostream> #include <cstdio> #define INF 3000005 //头文件+预处理名。 using namespace std; int n,a[Inf],q[INF],r,f[INF]; //定义变量,其中a数组表示输入的数,q数组表示存下标的单调栈,f数组是存结果。 int main() { scanf("%d",&n); for (int i=1; i<=n; i++) scanf("%d",&a[i]); //上面是读入。 for (int i=n; i>=1; i--) { //从最后开始枚举。 while (a[i]>=a[q[r]] && r>0) r--; //如果说它找到了比矮高的人并且不是最后,那么将那个矮的人弹掉,毕竟后面也不会有人看到它了,因为如果看到它的话那么必定可以看到比它前面的还比它高的。 f[i]=q[r]; //存结果,因为要正向输出。 q[++r]=i; //将它入栈。 } for (int i=1; i<=n; i++) printf("%d ",f[i]); //最后正着输出。 return 0; }
专项题
poj3494[求最大全1子矩阵]
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; int n,m,h[100005],top,ans; struct Node { int h,sta;//sta表示高度h的起始下标 }s[100005]; int main() { int hh,t; while(scanf("%d%d",&n,&m)==2) { memset(h,0,sizeof(h)); ans=0; for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { scanf("%d",&hh); h[j]=hh==0?0:h[j]+1; } t=m; h[++t]=-1;//令最后一个元素的下一个高度为-1,避免循环完毕后还要弹出栈中所有元素 s[0].h=-1; s[0].sta=top=0; for(int k=1;k<=t;++k) { if(h[k]>=s[top].h) { s[++top].h=h[k]; s[top].sta=k;//其起始下标就是自己的下标 } else { while(h[k]<s[top].h) { ans=max(ans,(k-s[top].sta)*s[top].h); --top;//弹出栈顶元素 } s[++top].h=h[k];//其起始下标是弹出的最后一个元素的起始下标 } } } printf("%d\n",ans); } return 0; }
单调队列
基本作用:解决滑动窗口的问题,这个问题是这样的:有一数列{an}和m个区间[L(i),R(i)],满足L(i)<=L(i+1),
R(i)<=R(i+1),对每个区间求区间最大值。时间复杂度O(n)
模板题
洛谷题库P1886[滑动窗口]
以求最小值为例观察队列中元素离开队列的情况:
1. 元素Vi从队尾离开队列:
i < j 且 Vi >= Vj,Vi没有参与求min的必要
2. 元素Vi从队首离开队列:
Vi超出了当前窗口的范围。
#include<cmath> #include<queue> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int a[1000001],n,wide,q[1000001],head,tail; int main(){ scanf("%d%d",&n,&wide); for(int i=1;i<=n;i++) scanf("%d",&a[i]); head=1;tail=0; for(int i=1;i<=n;i++) { while(tail>0&&head<=tail&&i-q[head]>=wide)//对于超出窗口范围的元素,出队 head++; while(tail>0&&head<=tail&&a[q[tail]]>a[i])//对于比新元素更大的元素,出队 tail--;//通过这个操作能始终保持范围内最小的元素位于队首,且队列里的元素是单调递增的 q[++tail]=i;//入队 if(i>=wide) printf("%d ",a[q[head]]);//在元素已经足够多个时输出 } head=1;tail=0; memset(q,0,sizeof(q)); printf("\n"); for(int i=1;i<=n;i++)//同上寻找最小值的方法寻找最大值 { while(tail>0&&head<=tail&&i-q[head]>=wide) head++; while(tail>0&&head<=tail&&a[q[tail]]<a[i]) tail--; q[++tail]=i; if(i>=wide) printf("%d ",a[q[head]]); } return 0; }
经典题
一本通1598[最大连续和]
●把前缀和当做值跑单调队列。
●求出前缀和sum[i],计算[l,r]区间和,就转化成了求sum[r]-sum[l-1]。
●枚举右端点i,则问题变为:找到一个左端点j,i−m<=j<=i−1且sum[j]最小。
●找到一个最小的前缀和(这样就可以保证这个区间内的和最大),用
sum[i]-这个最小前缀和sum[j]就是这个区间的最大值了,用ans来记录一下最大值即可。
#include <bits/stdc++.h> using namespace std; typedef int ll; inline ll read() { ll s=0; bool f=0; char ch=' '; while(!isdigit(ch)) { f|=(ch=='-'); ch=getchar(); } while(isdigit(ch)) { s=(s<<3)+(s<<1)+(ch^48); ch=getchar(); } return (f)?(-s):(s); } #define R(x) x=read() inline void write(ll x) { if(x<0) { putchar('-'); x=-x; } if(x<10) { putchar(x+'0'); return; } write(x/10); putchar((x%10)+'0'); return; } inline void writeln(ll x) { write(x); putchar('\n'); return; } #define W(x) write(x),putchar(' ') #define Wl(x) writeln(x) const int N=200005,inf=0x3f3f3f3f; int n,m,Qzh[N]; struct Record { int Shuz,Weiz; }Ddq[N]; int main() { // freopen("sum12.in","r",stdin); int i,Head=0,Tail=0,ans=-inf; R(n); R(m); for(i=1;i<=n;i++) { int x=read(); Qzh[i]=Qzh[i-1]+x; while(Head<Tail&&Ddq[Head].Weiz<i-m) Head++; ans=max(ans,Qzh[i]-Ddq[Head].Shuz); while(Head<=Tail&&Qzh[i]<=Ddq[Tail].Shuz) Tail--; Ddq[++Tail]=(Record){Qzh[i],i}; } Wl(ans); return 0; }
大家可以自行再搜些题目练习
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探