一、AcWing 828. 模拟栈
模板
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{
}
/*
栈:先进后出
队列:先进先出
*/
#include<iostream>
using namespace std;
const int N=100010;
int m,stk[N],tt;//tt始终指向栈中最后一个进去,最先出来的元素
//插入:stk[++tt]=x
//删除:tt--
//判断是否为空:tt>0
//栈顶:stk[tt]
int main()
{
cin >> m;
while (m -- )
{
string op;
int x;
cin >> op;
if (op == "push")
{
cin >> x;
stk[ ++ tt] = x;
}
else if (op == "pop") tt -- ;
else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;
else cout << stk[tt] << endl;
}
return 0;
}
二、单调栈
/*
给定一个长度为N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1
暴力做法 时间复杂度O(n^2)
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
cout<<-1<<' ';//数组第一个元素对应的输出一定是-1
for(int i=1;i<n;i++)//从数组中下标为1,第二个元素开始遍历
for(int j=i-1;j>=0;j--)//内层循环遍历外层遍历的数之前的所有数
if(a[j]<a[i]){
cout<<a[j]<<' ';
break;
}
else if(j==0){
cout<<-1<<' ';
}
return 0;
}
优化 如果i<j且a[i]>=a[j]则a[i]一定不会作为j后面的元素的答案输出
单调栈:遍历每个元素,入栈并保证栈的单调递减/增的性质
常见模型:找出每个数左边第一个比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )//可变
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
时间复杂度 O(n),因为每个元素一定会入栈一次,可能会出栈一次,总体来看是n级别
*/
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin>>x;
/*
找每个数左边第一个比它小的元素,所以是单调递增栈
如果求左边第一个比它大的元素,stk[tt]>=x改成<,仅此
每个元素入栈时必须保证它是栈中最大的元素,栈中不能有元素比它更大
所以删除之前单调栈栈顶元素的循环条件是栈非空并且栈顶元素大于等于它
*/
while (tt && stk[tt] >= x) tt --;
if (!tt) cout<<-1<<' ';
else cout<<stk[tt]<<' ';
//因为当前元素对应的输出一定是之前的某个数,所以一定要先输出栈顶元素再入栈
stk[ ++ tt] = x;
}
return 0;
}
三、数组模拟队列
AcWing 829. 模拟队列
普通队列模板
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{
}
循环队列模板
// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;
// 向队尾插入一个数
q[tt ++ ] = x;
if (tt == N) tt = 0;
// 从队头弹出一个数
hh ++ ;
if (hh == N) hh = 0;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh != tt)
{
}
单调队列 Acwing 154
模板
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
/*
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
*/
#include <iostream>
using namespace std;
const int N = 1000010;
int a[N], q[N];
int main()
{
int n, k;
cin>>n>>k;
for (int i = 0; i < n; i ++ ) cin>>a[i];
int hh = 0, tt = -1;
/*
队列中存储的是下标索引,这样才能时刻保证队列长度小于等于窗口
每次遍历是准备把一个元素的下标入队
但必须保证这个下标入队后,队列仍然单调递增(因为此时是求滑动窗口最小值)
*/
for (int i = 0; i < n; i ++ )
{
/*
首先判断当这个元素的下标入队后,当前的队头元素是否会滑出窗口
i - k + 1 代表当前元素的下标i入队后,队列大小为k的队头元素的下标
而且又因为每次遍历最多一个下标入队,一个下标出队,所以if就行,不需要模板的while
*/
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
//注意这里的q[tt]和i都是数组a的下标
while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
q[ ++ tt] = i;
/*
另外这里是先把下标入队之后再去判断是否要输出答案,所以不需要像单调栈一样
先考虑跳出while循环是否因为队列为空,是否要输出-1
*/
if (i >= k - 1) cout<<a[q[hh]]<<' ';
/*
前几个元素不需要输出答案,以及当前入队的下标对应的元素可能会是之后窗口的最小值
但此时窗口的最小值是队头元素这个下标所对应的元素
*/
}
//输出格式,换行注意
puts("");
hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
q[ ++ tt] = i;
if (i >= k - 1) cout<<a[q[hh]]<<' ';
}
puts("");
return 0;
}