单调栈(每日一类)

        这是上节课模拟队列的代码

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010;
int a[N];
int t, h;

void init(){
    t = -1;
    h = -1;
}

void push(int x){
    t ++;
    a[t] = x;
}

void pop(){
    h ++;
}

bool empty(){
    if(t == h) return true;
    else return false;
}

int query(){
    return a[h + 1];
}

int main(){
    int n;
    cin >> n;
    string s;
    int x;
    init();
    for(int i = 0; i < n; i ++ ){
        cin >> s;
        if(s == "push"){
            cin >> x;
            push(x);
        }
        if(s == "pop"){
            pop();
        }
        if(s == "empty"){
            if(empty()){
                cout << "YES" << endl;
            }
            else{
                cout << "NO" << endl;
            }
        }
        if(s == "query"){
            int ans;
            ans = query();
            cout << ans << endl; 
        }
    }
    return 0;
}

        大家自行参考,学习。

        终终终于把最基础的链表,栈,队列给学习完了,现在我们看一些具体的应用实践。今天我们讲单调栈。

        首先,我们大家要理解一下,上面说的数据结构不是固定的,数据结构的定义特点都是相同的,但是具体的实现方式却不尽相同。换句话说,只要是能实现并满足数据结构的定义特点的方式,都可以代表当前的数据结构,黑猫白猫,抓到耗子就是好猫。所以栈,队列并不是之前讲到的那种固定的格式。

        那好,我们开始讲单调栈:

        单调栈的应用场景通常有两种:1. 找出每个数前面第一个比它小的数 2. 找出每个数前面第一个比它小的数的下标。其他的都大同小异了。

        我们来看一下这道题 单调栈

        这道题就是经典的第一种类型的题目。

        老规矩,第一步,我们理解一下题目,嗯,很简单,找出每个数前面第一个比它小的数,如果有,就输出,如果没有,就输出-1,那怎么实现的,朴素的做法肯定是将数一个个存入数组中,然后以这个数为起点,从前往后找。

        假设存入3,4,2,7,5这五个数字,现在存到“4”这个数组,我们需要向前遍历数字,找到第一个比7小的数,也就是3,输出3,之后再存入2,找到第一个比2小的数,没有,那就输出-1,之后以此类推,我们看一下时间复杂度(简单来看,其实就是遍历两遍,就是n的平方)

        第二步,用编程语言表示出来,首先我们想到的肯定是数组,用数组存入,设置指针从前往后存入,然后从当前指针的位置向前遍历。代码如下:

#include <iostream>

using namespace std;

const int N = 100010;

int stk[N], tt;

int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> stk[i];
        int j = i - 1;
        while(stk[j] >= stk[i] && j) j --;
        if(j == 0)printf("-1 ");
        else printf("%d ",stk[j]);
    }
    return 0;
}

        但是这样的时间复杂度是O(n方)比较大,为了降低时间复杂度,我们需要做第三步优化

        第三步,由于我们对于这些数的操作中只关心第一个比当前插入的数小的数,再往之前的数就不会被考虑到了,比如3,4,2这三个数中,如果之后还要继续插入并查询数字,那么只会查询到2,或者返回-1,而不会出现3,4,所以我们操作的时候就可以将位置靠前(假定下标小的位置是靠前)并且后面还有较它小的数覆盖掉。实现的代码如下:

#include <iostream>

using namespace std;

const int N = 100010;

int stk[N], tt;

int main()
{
    int n;
    cin >> 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;
}

        代码中有两点要充分理解,第一,在这个代码与之前的不同就是将指针 j 变成了指针 tt ,从普通数组变成了一个栈(这点其实不太重要,因为栈本质上都是用数组来实现的)这个代码中最重要的一点还是这个栈的数的范围,是从下标为1的点到下标为 tt 的点,tt 本质上就是一个栈顶指针,它可以将不需要的数弹出这个栈,这个是理解这个单调栈最核心的一步。第二,在while (tt && stk[tt] >= x) tt -- ;这行代码中,前面的 tt 是来判断栈不为空的,这个和我们之前的栈的判空不太相同,这个也是另一种比较主流的判空,由于 tt 代表的是栈顶指针,在这个栈中栈底不会变化,所以当栈顶指针 tt 指向 0 时表示栈为空。

        那么,tt 栈顶指针是如何将将位置靠前并且后面还有较它小的数弹出栈的呢?还是拿  3  4  2  5  7 这五个数字举例,代码先将 3 输入x内,之后查找,由于 tt = 0,发现栈为空,那么就输出 -1 ,之后使 tt 加一,并把 3 的值赋给下标为 1 的位置;下一个, 4 输入x内,这时 tt 在 3 的位置,3 小于 4,于是把 3 输出;下一个, 2 输入x内,这时 tt 在 4 的位置,不满足条件,于是 tt - 1 ,向前查找,注意这时栈顶指针的位置已经改变,这意味着 4 已经被弹出栈,以此类推,3 也被弹出栈,此时 tt = 0,栈空,输出 -1。之后的数字也就大同小异。

        到这里,相信大家基本就将单调栈理解了

        第四步,debug,跳过。

        留一下课后练习吧,写出来 找出每个数前面第一个比它小的数的下标 这种类型的单调栈,我们刚刚讲的是 找出每个数前面第一个比它小的数 。本质上其实还是一样的方法,一样的理论。

        下课!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值