浅谈单调队列和单调栈

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)。

posted @   RZC大蒟蒻  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示