• 【leetcode 力扣刷题】栈—波兰式///逆波兰式相关知识和题目


    波兰式、逆波兰式介绍

    我们常看到的四则运算的计算式,比如2+3*(4-9),称为中缀表达式,人类去计算的时候知道这些运算符是有优先级的:()> */ > +-,但是让计算机去运算就有歧义了。上面的式子是很简单的,实际可以遇到很多层括号,计算机不会去括号的。因此就有了波兰式和逆波兰式。
    波兰式和逆波兰式里,没有括号,计算没有歧义。
    波兰式,也称为前缀表达式,即运算符在前面,数字在后面,上面的计算式转换成波兰式后为+2*3-49。
    逆波兰式,也称为后缀表达式,即运算符都在后面,数字在前面,上面的计算式转换成逆波兰式后为2349-*+。

    常规表达式转换成逆波兰式

    可以通过添括号、开括号中缀表达式变成逆波兰式,依旧以上面的式子为例子,添括号是指对应每个运算数和每次运算都添加一层括号,上式添括号后变成((2) + ((3) * ((4) - (9))))。然后从最里面一层括号开始,去括号,并将运算符放在数字后面:

    • 1、((2) + ((3) * (49-)))
    • 2、((2) + (349-*))
    • 3、(2349-*+)
    • 4、2349-*+

    编程让常规表达式转换成逆波兰式

    如何让计算机将中缀表达式变成逆波兰式呢? 转换成逆波兰式最重要的是运算符的顺序,需要考虑括号、优先级、以及左右顺序。转换过程中用一个栈保存遍历过程中遇到的运算符。从左到右遍历表达式的时候,遇到运算数,直接加入到结果表达式中;遇到运算符,需要入栈或者出栈:

    • 如果当前运算符是’)‘,即括号内运算结束了,那么一直到栈内的’(',所有的运算符都出栈;
    • 如果当前运算符是’(‘,表示括号内运算开始,’('直接入栈;
    • 如果当前栈顶运算符是’(',当前运算符直接入栈;
    • 如果当前运算符等级高于栈顶运算符等级,直接入栈;比如当前是’*‘,栈顶是’+',直接入栈;
    • 如果当前运算符等级低于或者等于栈顶运算符等级,就出栈,直到栈空 or 栈顶运算等级更低,当前运算符入栈;

    遍历完计算式后,如果栈不空将栈内运算符依次取出加入逆波兰式中,依旧以上面的2+3*(4-9)为例,转换成逆波兰式的过程如下:

    原计算式说 明逆波兰式计算式~栈~
    2+3*(4-9)2—运算数直接加入结果中2
    +3*(4-9)+—运算符且栈空,直接入栈2+
    3*(4-9)3—运算数直接加入结果中23+
    *(4-9)*—运算符且比栈顶+等级高,直接入栈23+*
    (4-9)(—直接入栈23+*(
    4-9)4—运算数直接加入结果中234+*(
    -9)- —运算符,且栈顶是(,直接入栈234+*(-
    9)9—运算数直接加入结果中2349+*(-
    ))—运算符,前面到(都出栈加入结果中2349-+*
    将栈内运算符全都加入结果中2349-*+

    代码(C++)【不确定是否正确……】:

    //判断优先级
    int operator_priority(char ch){        
        if (ch== '+' || ch == '-')
            return 1;
        if (ch == '*' || ch == '/')
            return 2;
        if(ch == '(')
            return 0;
        return 0;
    }
    //判断是否是操作符
    bool is_operator(char ch){
        return (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')');
    }
    
    //转换成逆波兰式 
    vector<string> RPN(string s){
        vector<string> tokens;
        string operators;
        for(int i = 0 ; i < s.size() ; ){
            //操作符
            if(is_operator(s[i])){
                //如果是) 直到遇到( 操作符一直出栈
                if(s[i] == ')'){
                    while(operators.back()!='('){
                        tokens.emplace_back(string(1,operators.back()));
                        operators.pop_back();
                    }
                    operators.pop_back();
                    i++;
                }
                //操作符栈为空 或者 栈顶为( 或者 当前为( 直接入栈
                else if(operators.empty() || operators.back() == '(' ||s[i] == '(')
                    operators.push_back(s[i++]);
                //当前操作符优先级更高 直接入栈
                else if(operator_priority(s[i]) > operator_priority(operators.back()))
                    operators.push_back(s[i++]);
                //当前操作符优先级更低或者一样 前面的出栈    
                else{
                    do{
                        tokens.emplace_back(string(1,operators.back()));
                        operators.pop_back();
                    }while(operator_priority(s[i]) <= operator_priority(operators.back()));
                    operators.push_back(s[i++]);
                }
            }     
            //操作数     
            else {
                int start = i;
                do{
                    i++;
                }while(i<s.size() && !is_operator(s[i]));
                //操作数可能不止一位
                tokens.emplace_back(s.substr(start,i - start));
            }
        }
        while(!operators.empty()){
            tokens.emplace_back(string(1,operators.back()));
            operators.pop_back();            
        }
        return tokens;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    逆波兰式运算过程

    计算逆波兰式的时候,从前往后遍历式子,遇到运算符的时候,对其前面紧跟的两个运算数进行运算:

    • 2349-*+从前往后遍历先遇到’-',然后计算得到23(-5)*+;
    • 23(-5)*+继续往后遍历遇到’*',计算得到2(-15)+;
    • 2(-15)+继续往后遍历遇到’+',计算得到-13即为答案;

    常规表达式转换成波兰式

    可以通过添括号、开括号把中缀表达式变成波兰式,依旧以上面的式子为例子,添括号是指对应每个运算数和每次运算都添加一层括号,上式添括号后变成((2) + ((3) * ((4) - (9))))。然后从最里面一层括号开始,去括号,并将运算符放在数字前面:

    • 1、((2) + ((3) * (-49)))
    • 2、((2) + (*3-49))
    • 3、(+2*3-49)
    • 4、+2*3-49

    编程让常规表达式转换成波兰式

    !!!还不会!!!待解决……
    或许是按照逆波兰式的解法,只是从后向前遍历原计算式,最后得到的结果再reverse一下(?)

    波兰式运算过程

    计算波兰式的时候,从后往前,遇到运算符的时候,对其后面紧跟的两个运算数进行运算:

    • +2*3-49从后往前最先遇到’-',运算后变成+2*3(-5)
    • +2*3(-5)继续向前遍历遇到’*',运算后变成+2(-15)
    • +2(-15)继续向前遍历遇到’+',运算后得到-13,即为答案

    150. 逆波兰式表达式求值

    题目链接:150. 逆波兰式表达式求值
    题目内容:
    在这里插入图片描述
    实际就按照逆波兰式的计算方法,遍历逆波兰式,遇到运算数就放入栈,遇到运算符就依次取栈顶元素,取两次,得到运算数num1和num2,做运算后将结果压入栈中;直到遍历完逆波兰式,得到的就是结果。
    需要注意,num1和num2的四则运算,加法和乘法,两个数可以交换左右顺序,但是在减法和除法中,num1 - num2 ≠ num2 - num1,需要注意第一个从栈顶取出的是num2,之后取的是num1。
    代码如下(C++):

    class Solution {
    public:
        int evalRPN(vector<string>& tokens) {
            stack<int> num;
            for(int i = 0; i < tokens.size(); i++){
                //运算数直接入栈
                if(tokens[i] != "+" && tokens[i] != "-" && tokens[i] != "*" && tokens[i] != "/"){
                	//需要将string转换成int数字
                    num.push(atoi(tokens[i].c_str()));
                }
                else{
                    //注意先取的是nums2
                    int num2 = num.top();
                    num.pop();
                    //之后取的是nums1
                    int num1 = num.top();
                    num.pop();
    				//根据运算符做运算
                    switch(tokens[i][0]){
                        case '+':
                            num.push(num1 + num2);
                            break;
                        case '-':
                            num.push(num1 - num2);
                            break;
                        case '*':
                            num.push(num1 * num2);
                            break;
                        case '/': 
                            num.push(num1 / num2);
                            break;
                    }
                }
            }
            //最后压入栈的就是答案
            return num.top();
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    224. 基本计算器

    题目链接:224. 基本计算器
    题目内容:
    在这里插入图片描述
    提示里需要注意的是,这个题目的运算只有加减,没有乘除。在只有加减的情况下,这个题目就单纯考察怎么开括号了。加法和减法优先级是一样的,括号对加法是没有用的,即(2+3) + (5-2)实际(5-2)的括号不加也行——(2+3) +5 -2;但对于减号却不行,(2+3) - (5-2),如果要去掉括号,就变成了(2+3) -5 +2,括号前面的减号,打开括号后,括号内+ 会变成-,-会变成+。并且这个效应会随着括号以及减号的累加而累加,比如-(…-(…-()…)…)这样的三重括号,第一层括号内符号全部要变,乘-1;第二层括号内又要全部乘-1,由于第一层括号已经乘了-1了,最终第二层括号内的就负负得正;最内层括号外面有三个-,因此最终还是会乘-1。
    因此本题的重点在于开括号的时候,记录括号前面是+还是-,是+正常运算,是-就需要乘以-1。
    实现代码(C++):

    class Solution {
    public:
        int calculate(string s) {
            int sign = 1;
            stack<int> ops;
            //记录括号前的符号,1表示加,-1表示减
            ops.push(1);
            int ans = 0;
            int i = 0, n = s.size();
            //遍历字符串s
            while(i < n){
                if(s[i] == ' '){
                    i++;
                }
                //如果是加号,紧接着的运算数是+还是-,需要看该层括号外对应的符号ops
                else if(s[i] == '+'){
                    sign = ops.top();
                    i++;
                }
                //如果是减号,后面数字的运算是+ or -,取决于括号前面的ops,且要反号
                else if(s[i] == '-'){
                    sign = -ops.top();
                    i++;
                }
                //如果是左括号,表示遇到新的一层括号,当前的sign即为这个括号前的符号,入栈
                else if(s[i] == '('){
                    ops.push(sign);
                    i++;
                }
                //如果是右括号,表示一层括号结束,pop掉对应的符号
                else if(s[i] == ')'){
                    ops.pop();
                    i++;
                }
                //是数字,就做相应的运算
                else{
                    long num = 0;
                    while(i < n && s[i] >= '0' && s[i] <= '9'){
                        num = num*10 + s[i] - '0';
                        i++;
                    }
                    ans += sign * num; //需要乘以sign,sign决定了这个数是加法还是减法
                }
            }
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    如果题目中还有乘法除法,以及括号表示不同的优先级,可以将表达式转换成前缀表达式或者后缀表达式,即波兰式或者逆波兰式,然后开始运算。

    227. 基本计算器Ⅱ

    题目链接:227. 基本计算器Ⅱ
    题目内容:
    在这里插入图片描述
    这个题目没有括号!只需要考虑加减乘除的优先级。因为乘法和除法优先级更高,在整个算式中应该先去计算乘法和除法,那我们就这么做!遍历字符串s的时候做如下操作:

    • 如果是运算符,就记录该运算符【等到取到了其紧跟的数字,对其进行相应运算】;
    • 如果是数字,那么就找到这个数字的终点,得到一个数字;
    • 这个数字要做何操作,取决于前面的操作符,如果是乘or除,就用前面一个数与这个数字做相应运算,结果保存;
    • 如果是加法,直接保存这个数;如果是减法,保存这个数的负数;

    因为整个算式,第一个数字前如果有负号,那就保存其负数;如果第一个数是正数呢?因此我们要先给第一个数字一个初始化的操作符号’+'。 另外要注意遇到空格直接跳过。
    最终将保存的数字全部都加起来即可。因为在遍历s的过程中已经先做了乘除以及减法了,最后统一做加法。
    代码如下(C++):

    class Solution {
    public:   
        int calculate(string s) {
            int idx = 0, n = s.size();
            //用于存算式中的数字
            vector<int> nums;
            //num用于计算s中的每个不止一位的数字,比如321,需要先遍历到3然后是2然后是1
            long num = 0;
            //保存每个数字前面的操作符
            char opt = '+';
            //遍历s
            while(idx < n){
            	//如果是空格直接跳过
                if(s[idx] == ' '){
                    idx++;
                    continue;
                }
                //如果是数字
                if(s[idx] >= '0' && s[idx] <= '9'){
                	//计算这个数字
                    num = 0;
                    do{
                        num = num*10 + s[idx] -'0';
                        idx++;
                    }while(idx<n && s[idx] >= '0' && s[idx] <= '9');
    				//根据这个数字前面的操作符来保存
                    switch(opt){
                        case '+': nums.emplace_back(num);
                                  break;
                        case '-': nums.emplace_back(-num);
                                  break;
                        case '*': nums.back() *= num;
                                  break;
                        case '/': nums.back() /= num;
                                  break;
                    }
                }
                //操作符
                else{
                    opt = s[idx];
                    idx++;
                }
            }
            num = 0;
            //将保存的数都加起来得到结果
            for(int i = 0; i < nums.size(); i++)
                num += nums[i];
            return num;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    282. 给表达式添加运算符

    题目链接:282. 给表达式添加运算符
    题目内容:
    在这里插入图片描述
    这个题目我是完全不会做……看的题解,然后试图理解……再自己试着写一写代码和题解。
    题目里说在数字之间添加运算符,实际上可以添加也可以不添加,因此针对每两个数字之间的位置,有4种选择——不添加,或者添加+、-、*中的一个。此题用回溯法解题,时间复杂度是O(4^n)。
    用回溯法解题的思路如下:

    • 对于每两个数字之间不添加or添加以及添加什么,有四种选择:
    1. 什么都不添加:更新之前表达式的最后一个数字num1,假设当前数字是num2,num1=num1*10+num2,同时更新之前的表达式结果val = val - num1(旧) + num1(新)。
    2. 添加一个’+':更新之前表达式,加上当前的数字num2,表达式的值val = val + num2;
    3. 添加一个’-':更新之前表达式,减去当前的数字num2,表达式的值val = val - num2;
    4. 添加一个’*‘:更新之前表达式,同时注意’*‘优先级更高,表达式最后一个数num1,不管这个数之前是’+‘还是减’-‘还是乘’*',表达式的值先减去val,再加上num1*num2;
    • 进行深度搜索的结束条件是,遍历完字符串的时候,如果val == target就将当前的表达式加入结果数组中;
    • 由于每一步更新表达式值的时候,可能涉及到上一步表达式的最后一个数字的操作,因此在递归调用函数的时候需要将num1传递下去;表达式要一直增加,因此要传递表达式;表达式的值也需要更新,因此要传递val;添加什么操作符也需要传递。

    代码如下(C++)——抄的官方题解,真不会啊………………啊啊啊啊:

    class Solution {
    public:
        vector<string> addOperators(string num, int target) {
            int n = num.length();
            vector<string> ans;
    
            function<void(string&, int, long, long)> backtrack = [&](string &expr, int i, long res, long mul) {
                if (i == n) {
                    if (res == target) {
                        ans.emplace_back(expr);
                    }
                    return;
                }
                int signIndex = expr.size();
                if (i > 0) {
                    expr.push_back(0); // 占位,下面填充符号
                }
                long val = 0;
                // 枚举截取的数字长度(取多少位),注意数字可以是单个 0 但不能有前导零
                for (int j = i; j < n && (j == i || num[i] != '0'); ++j) {
                    val = val * 10 + num[j] - '0';
                    expr.push_back(num[j]);
                    if (i == 0) { // 表达式开头不能添加符号
                        backtrack(expr, j + 1, val, val);
                    } else { // 枚举符号
                        expr[signIndex] = '+'; backtrack(expr, j + 1, res + val, val);
                        expr[signIndex] = '-'; backtrack(expr, j + 1, res - val, -val);
                        expr[signIndex] = '*'; backtrack(expr, j + 1, res - mul + mul * val, mul * val);
                    }
                }
                expr.resize(signIndex);
            };
    
            string expr;
            backtrack(expr, 0, 0, 0);
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 相关阅读:
    c#与汇川plc通信 使用官网API库
    linux网络编程epoll详解
    卷积网络识别好莱坞明星
    基于Go语言Iris+Xorm搭建MVC项目教程
    CORE EMU初接触
    让 GPT-4 来 review 开源社区贡献者的 PR - 每天5分钟玩转 GPT 编程系列(5)
    基于Google大型分布式系统的网格化实战
    【vue2第八章】工程化开发和使用脚手架和文件结构
    JAVA堆中的对象结构
    【机器人学导论(第四版)】1-绪论
  • 原文地址:https://blog.csdn.net/massive_jiang/article/details/132866493