栈-栈的实现/栈的应用(栈混洗,表达式求值,括号匹配,进制转换,最小/大栈,单调栈)...

栈(stack)是一种先进后出的数据结构

内部api接口:

top();//获取栈顶元素值
push();//放入元素到栈顶
pop();//将栈顶元素弹出
size();//元素个数
isEmpty();//是否为空

接口实现

使用向量(动态数组/顺序表)实现

将tail初始化为0,每增加一个元素,tail++,删去一个元素tail--,tail-1即为栈顶元素

用size来记录元素个数

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

template<typename T>
class Stack{
private:
    vector<T>ves;
    int size;
    int tail;
    int capacity;
public:
    void push(T const&e){
        if(size>=capacity){
            ves.reserve(capacity*2);
            capacity = capacity*2;
        }
        size++;
        ves[tail] = e;
        tail++;
    }
    Stack():size{0},tail{0},capacity{5}{
        ves.reserve(capacity);
    }
    bool isEmpty(){
        return size==0;
    }
    bool pop(){
        if(isEmpty())return false;
        tail--;
        size--;
        return true;
    }
    T top(){
        if(!isEmpty()){
            return ves[tail-1];
        }
        return -1;
    }
};
int main()
{
    Stack<int> s;
    for(int i = 0;i<5;i++){
        s.push(i);
    }
    while(!s.isEmpty()){
        cout<<s.top()<<endl;
        s.pop();
    }
    cin.get();
    return 0;
}

逆序输出类问题

1. 进制转换

短除法:
每次整除要转换的进制数,将其余数记录,并继续整除其商image

最后自底向上将余数记录,即为转换后的值

//base为进制,n为要转换的整数
void convert(stack<char>&s,int n,int base){
    static char digit[] = {'0','1','2','3','4','5','6','7','8','A','B','C','D','E','F'};
    while(n){
        s.push(digit[n%base]);//余数入栈。余数的值对应digit数组中的值
        n/=base;
    }
}

递归嵌套类问题

1. 括号匹配

image

消去一对紧邻的左右括号,不会影响全局的匹配判断

即为:[L] ( ) [R]匹配。仅当[L] [R]匹配

顺序扫描表达式,用栈记录已经扫描的部分,遇到左括号就入栈,遇到右括号且栈顶匹配就将栈顶出栈,这样消去的匹配的一对就是紧邻的一对左右括号

如果最后一个右括号被扫描到之前,栈变空,或扫描后,栈非空,结果都表示括号不匹配

遍历后,栈空当且仅当匹配

bool isValid(string s){
    stack<char>c;
    for(int i = 0;i<s.size();i++){
        if(s[i]=='('||s[i]=='{'||s[i]=='['){   //遇到左括号则进栈
            c.push(s[i]);
        }
        else{   //如果s[i]为右括号
                if(c.empty()) return false; //如果遇到右括号且此时栈已空
                if((c.top()=='('&&s[i]!=')')||(c.top()=='['&&s[i]!=']')||(c.top()=='{'&&s[i]!='}')){  //如果是某种右括号且与对应的栈顶匹配,则栈顶出栈
                    return false;
                }
                c.pop();
        }
    }
    return c.empty();
}

如果是判断单种括号,可以使用一个计数器,在遇到左括号时+1,遇到右括号且此时计数器非>0时,计数器-1,直到最后计数器是否为0,大体结构与使用栈判断相同

但是计数器的方法不能用于多种括号并存的情况

包含min/max函数的栈(最小/最大栈)

设计一个支持push,pop,top,getMin操作的栈

思路:使用两个栈,令第二个栈在栈顶永远保存的是此时第一个栈中的最小元素值

两个栈的元素是一一对应的,出栈是同时将栈顶出栈

但是入栈时,有不同之处,例如:
入栈序列 -2 0 -3

第一个栈: -3 0 -2] 第二个栈由于始终在栈顶保存当前栈最小值: -3 -3 -2]

主要考察的是getMin方法的实现,所以直接用标准库中的栈也可以

class MinStack {
private:
    stack<int>s1;
    stack<int>s2;
public:
    MinStack() {}
    void push(int val) {
        s1.push(val);
        if(s2.empty()){
            s2.push(val);
        }
        else{
            if(val<=s2.top()) s2.push(val);
            else s2.push(s2.top());
        }
    }
    void pop() {
        s1.pop();
        s2.pop();
    }
    int top() {
        return s1.top();
    }
    int getMin() {
        return s2.top();
    }
};

栈的混洗/判断栈的压入弹出序列是否正确

栈的输入,弹出序列

如果输入序列为n个元素,则混洗序列个数可能最多有:

image

k的取值为1-n

因为1这个元素可能是第一个被推入左侧栈中的元素,最极端也可能是第n个

上述的栈混洗的个数即为catalan(n) = (2n)!/((n+1)!*n!)

如何判断一个弹出序列是输入序列的一个栈混洗

image

上图的条件称为禁形

是一个序列是否为栈混洗的充分必要条件

判断方式:
引入三个栈,模拟栈的混洗过程,每次s.pop之前,检测s是否为空,或者需要弹出的元素在s中并非栈顶元素

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
//如果扫描完popped之后,栈的全部元素都弹出,即栈为空,则返回true,这是栈的一个弹出序列
        // 根据popped序列从左到右的顺序,决定栈的弹出和放入操作
        // 如果当前push之后,栈顶元素不==poped[i],则继续push,如果等于则pop,且i++
        // 去判断下一个出栈早的元素
        stack<int>s;
        int j = 0;
        for(int i = 0;i<popped.size();i++)
        {
            s.push(pushed[i]);
            while(!s.empty()&&s.top()==popped[j]){//因为可能push进了很多元素,但是直到某一元素才和popped左侧相等,这个元素弹出后,之前的元素又和popped更右侧一个相等        //j不能越界,这里要用到&&运算符一边错误则全错的特性  
                    s.pop();
                    j++;
            }
        }
        if(s.empty())return true;
        else return false;
    }
};

一个重要性质:n对括号所能构成的合法表达式的个数 = n个元素的栈混洗个数

逆波兰表达式求值/中缀表达式求值

(1)直接对中缀表达式进行求值

需要使用两个栈:一个放置运算数,一个放置运算符

image

(2)RPN 逆波兰表达式

相比中缀表达式的有点:不适用括号即可表示带优先级的运算关系

:运算符在表达式序列中谁先出现则谁优先计算

若表达式处理完毕且表达式正确则栈最后只剩一个元素即为结果

将操作数一次入栈,遇到操作符则将需要的操作数依次出栈,并将运算结果再次入栈

(1)首先要将中缀表达式转换为后缀表达式:image

所有操作数在后缀和中缀表达式中的相对次序是不变的

image

image

image

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int>s;
        for(int i = 0;i<tokens.size();i++){
            if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/"){
                int a = s.top();
                s.pop();
                int b = s.top();
                s.pop();
                if(tokens[i]=="+") s.push(a+b);
                if(tokens[i]=="-") s.push(b-a);
                if(tokens[i]=="*") s.push(b*a);
                if(tokens[i]=="/") s.push(b/a);     
            }
            else {
                s.push(stoi(tokens[i]));
            }
        }
        return s.top();
    }
};

单调栈

Leetcode 739. 每日温度

维护一个从始至终都为单调递减的栈(从栈底到栈顶)

只不过这里压入栈的是每天温度的下标

如果当前扫描到的元素大于栈顶元素,这个元素即为此序列中第一次大于栈顶的元素

所以下标相减,就是等待的天数

如果小于栈顶,则将下标入栈

具体的图解:

LeetCode 图解 | 739.每日温度 - 每日温度 - 力扣(LeetCode) (leetcode-cn.com)

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int>ans(n,0);
        stack<int>s;
        for(int i = 0;i<n;i++){
            while(!s.empty()&&temperatures[i]>temperatures[s.top()]){
                ans[s.top()] = i-s.top();
                s.pop();
            }
            s.push(i);
        }
        return ans;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值