栈与队列(手动实现)


栈与队列

栈(先进后出)

​ 栈的一个重要特性就是先进后出,

  • 数组模拟栈

模板

// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{

}

例题

828. 模拟栈 - AcWing题库

#include <iostream>
using namespace std;
const int N=1e5+10;
int m;
int stk[N],tt;
int main(){
    cin>>m;
    while(m--){
        string op;
        cin>>op;
        if (op=="push")cin>>stk[++tt];
        else if(op=="pop")tt--;
        else if(op=="empty")cout<<(tt?"NO":"YES")<<endl;
        else cout<<stk[tt]<<endl;
    }
    return 0;
}

3302. 表达式求值 - AcWing题库


队列(先进先出)

解析

模板

  • 普通队列
// 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)
{

}

例题

829. 模拟队列 - AcWing题库

#include <iostream>
using namespace std;
const int N=1e5+10;
int a[N],hh=0,tt=-1;
int main(){
    int n;
    cin>>n;
    while(n--){
        string op;
        cin>>op;
        int x;
        if(op=="push"){
            cin>>x;
            a[++tt]=x;
        }else if(op=="pop"){
            hh++;
        }else if(op=="empty"){
            if(tt<hh)cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
        }else{
            cout<<a[hh]<<endl;
        }
    }
    return 0;
}

单调栈和单调队列

单调栈

​ 单调栈对应题型——给定一个序列,求序列中每一个数左(右)边,离它最近的且比它大(小)的数。

模板&例题

830. 单调栈 - AcWing题库

​ 很容易能想到的暴力做法,使用两重循环,对每一个数向左进行查找,找到的第一个比当前数小的就输出找不到就“-1”然后进行下一个循环。当然这样就很容易超时。

​ 可以使用一个栈来保存经过的数字,每次查找的时候就向前出栈找,这样可以发现一个性质,要是存在一个数据段a1,a2,a3,其中a1大于a2,那么a2是不需要a1作为答案的而且a3或之后的数也不需要a1作为答案,因为要找也找比a1小的a2。这时候我们就可以确定a1是不会作为答案出现的,所以直接出栈就好。

​ 这个的时间复杂度看似有两重循环,但是一重循环内的入栈出栈操作每个数只会出现一次,所以实际时间复杂度是O(n)级别的。

#include <iostream>
using namespace std;
const int N=100010;
int stk[N],tt;
int main(){
    int n;
    scanf("%d",&n);
    while(n--){
        int x;
        scanf("%d",&x);
        while(tt&&stk[tt]>=x)tt--;
        if(!tt)printf("-1 ");
        else printf("%d ",stk[tt]);
        stk[++tt]=x;
    }
    return 0;
}

131. 直方图中最大的矩形 - AcWing题库


单调队列

解析

模板&例题

154. 滑动窗口 - AcWing题库

​ 可以使用一个队列来维护,每次一个数入队,要是当前队列长度超过窗口,就将队首元素弹出,以此保证窗口的稳定。

​ 从数据上来看,要是直接暴力跑时间复杂度将会是 O ( 1 0 6 ∗ 1 0 6 ) O(10^6∗10^6) O(106106)绝对超时,所以还要想办法优化。和单调栈一样找一个数据段 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3,其中 a 1 < a 2 > a 3 a1<a2>a3 a1<a2>a3,只要队列中存在这样的情况,在放入 a 3 a_3 a3 的时候就将在 a 3 a_3 a3 前面并且比 a 3 a_3 a3 小的 a 1 , a 2 a_1,a_2 a1,a2 从队列中删除。

下标01234567
13-1-35367

i = 0 i=0 i=0

h h = 0 , t t = − 1 、 hh=0,tt=-1、 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;//q[0]=0;将a[0]放入队列
    if(i>=k-1)printf("%d ",a[q[hh]]);//队列尚未第一次满
}

h h = 0 , t t = 0 , q [ h h ] = 0 , q [ t t ] = 0 hh=0,tt=0,q[hh]=0,q[tt]=0 hh=0,tt=0,q[hh]=0,q[tt]=0,队列中: a [ 0 ] a[0] a[0]

i = 1 i=1 i=1

h h = 0 , t t = 0 , q [ h h ] = 0 , q [ t t ] = 0 hh=0,tt=0,q[hh]=0,q[tt]=0 hh=0,tt=0,q[hh]=0,q[tt]=0,队列中: a [ 0 ] a[0] a[0]

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--;//队列非空,队尾大于等于当前值a[0]>=a[1]不成立,即队尾小于当前值,跳出。
    q[++tt]=i;//q[1]=1;将a[1]放入队列
    if(i>=k-1)printf("%d ",a[q[hh]]);//队列尚未第一次满
}

h h = 0 , t t = 1 , q [ h h ] = 0 , q [ t t ] = 1 hh=0,tt=1,q[hh]=0,q[tt]=1 hh=0,tt=1,q[hh]=0,q[tt]=1,队列中: a [ 0 ] , a [ 1 ] a[0],a[1] a[0],a[1]

i = 2 i=2 i=2

h h = 0 , t t = 1 , q [ h h ] = 0 , q [ t t ] = 1 hh=0,tt=1,q[hh]=0,q[tt]=1 hh=0,tt=1,q[hh]=0,q[tt]=1,队列中: a [ 0 ] , a [ 1 ] a[0],a[1] a[0],a[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--;//队列非空,队尾大于等于当前值a[1]>=a[2]成立,即队尾大于当前值,删除队尾,再次判断,仍然小于,继续执行,直到队列为空或遇到更小的,tt=-1。
    q[++tt]=i;//q[0]=2;将原来的a[0]覆盖,同时原来的a[1]也失效
    if(i>=k-1)printf("%d ",a[q[hh]]);//队列已满开始输出,输出a[q[hh]],即输出a[2]。
}

h h = 0 , t t = 0 , q [ h h ] = 2 , q [ t t ] = 2 hh=0,tt=0,q[hh]=2,q[tt]=2 hh=0,tt=0,q[hh]=2,q[tt]=2,队列中: a [ 2 ] a[2] a[2]

看到这一步就已经清晰了,每次while判断都会将最小的数放在最前面,因为是单调的即使窗口划出也是同样满足当前的队首也是最小值的

i = 3 i=3 i=3

h h = 0 , t t = 0 , q [ h h ] = 2 , q [ t t ] = 2 hh=0,tt=0,q[hh]=2,q[tt]=2 hh=0,tt=0,q[hh]=2,q[tt]=2,队列中: a [ 2 ] a[2] a[2]

for(int i=0;i<n;i++){
    if(hh<=tt&&i-k+1>q[hh])hh++;//队列非空,队首下标未超出,跳过(注意这里是q[hh],不是hh。即当前队列中的元素还有可以输出的,要是出现-3,-2,-1,0这样的情况,当窗口为3,要滑动到-2,-1,0的时候就要限定-3已经超过窗口了,这时就体现了这句的意义)。
    while(hh<=tt&&a[q[tt]]>=a[i])tt--;//队列非空,队尾大于等于当前值a[2]>=a[3]成立,即队尾大于当前值,删除队尾,tt=-1。
    q[++tt]=i;//q[0]=3;
    if(i>=k-1)printf("%d ",a[q[hh]]);//队列已满开始输出,输出a[hh],即输出a[3]。
}

h h = 0 , t t = 0 , q [ h h ] = 3 , q [ t t ] = 3 hh=0,tt=0,q[hh]=3,q[tt]=3 hh=0,tt=0,q[hh]=3,q[tt]=3,队列中: a [ 3 ] a[3] a[3]

当然选出最大值只要更改while判断的逻辑就行了

  • AC代码
#include <iostream>

using namespace std;
const int N=1e6+10;
int n,k;
int a[N],q[N];
int main(){
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)scanf("%d",&a[i]);

    int hh=0,tt=-1;//hh:队头,tt:队尾.
    for(int i=0;i<n;i++){
        //队列是否为空、队头是否已经划出窗口(队列中存的是下标)
        //因为每次窗口只会移动一位,所以不用while。
        if(hh<=tt&&i-k+1>q[hh])hh++;
		//不为空、当前队列里的值小于大于a[i]的都出去,(a[]是单调递增的,下面是单调递减)
        while(hh<=tt&&a[q[tt]]>=a[i])tt--;
        q[++tt]=i;

        if(i>=k-1)printf("%d ",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)printf("%d ",a[q[hh]]);
    }
    puts("");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值