浅谈单调队列和单调栈
1.单调队列
看一道模板题:
有一个长为 n 的序列 a,以及一个大小为 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。
其实就是给你一段数和一个区间,这个区间在这段书里边滑,求每次滑动后这个区间中的数的最大值。(最大值会了最小值自然就会了)
然后你发现狮虎可以暴力枚举。
发现时间复杂度差不多是O((n-k)•k)的。
然后发现过不了。
那么我们就要使用单调队列了。
单调队列是什么?
我们首先要明确两个观点:
1. 如果后一个数 ≥ 前一个数,那么前一个数就得出队;
2. 如果我们的队列的个数大于 k,那么我们就把最前面的数出队,直到队列个数小于 k。
单调队列算法需要两个数组:q 数组和 a 数组。a 数组就是输入的那个队列,然后 q 数组则是存队列里元素的编号的。所以说单调队列不直接存元素,而是存队列里元素的编号。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,k,a[1000005],q[1000005];
int main(){
scanf("%d%d",&n,&k);
int head=1,tail=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
while(head<=tail&&q[head]<=i-k) head++;
while(head<=tail&&a[q[tail]]>=a[i]) tail--;
q[++tail]=i;
if(i>=k)printf("%d ",a[q[head]]);
}
printf("\n");
head=1;tail=0; memset(q,0,sizeof(q));
for(int i=1;i<=n;i++){
while(head<=tail&&q[head]<=i-k) head++;
while(head<=tail&&a[q[tail]]<=a[i]) tail--;
q[++tail]=i;
if(i>=k) printf("%d ",a[q[head]]);
}
return 0;
}
可以发现,每个元素最多入队1次、出队1次,且出入队都是 O(1) 的,因此这是一个总时间 O(n) 的算法
2.单调栈
先来看一道例题
给出 n (n ≤ 3 × 1e6) 个数,求每一个数后面的第一个比它大的数的下标。
发现强做肯定不行,时间过不了。这时候就要用到单调栈。
单调栈要遵循一个基本原则:
我们从后往前搜。对于每一个点,我们要一直弹出位于栈顶还比它小的元素,然后将其加入队列。这个点的答案就是当前的栈顶。
#include<cstdio>
#include<stack>
using namespace std;
int n,a[3000005],f[3000005];//a是需要判断的数组(即输入的数组),f是存储答案的数组
stack<int>s;//模拟用的栈
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=n;i>=1;i--)
{
while(!s.empty()&&a[s.top()]<=a[i]) s.pop();//弹出栈顶比当前数小的
f[i]=s.empty()?0:s.top();//存储答案,由于没有比她大的要输出0,所以加了个三目运算
s.push(i);//压入当前元素
}
for(int i=1;i<=n;i++) printf("%d ",f[i]);//输出
return 0;
}
那么为什么要这样子做呢?
很简单(其实跟单调队列差不多),如果一个点比另一个点低并且它还在其后面,那么这个点就不会对前面的点产生任何影响。
时间复杂度是 O(n) 的,因为每一个点最多 进栈/出栈 一次,所以最多也就是 O(2n),省略常数就是 O(n)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】