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

news2024/11/29 22:41:58

波兰式、逆波兰式相关知识和题目

  • 波兰式、逆波兰式介绍
    • 常规表达式转换成逆波兰式
    • ==编程让常规表达式转换成逆波兰式==
    • 逆波兰式运算过程
    • 常规表达式转换成波兰式
    • ==编程让常规表达式转换成波兰式==
    • 波兰式运算过程
  • 150. 逆波兰式表达式求值
  • 224. 基本计算器
  • 227. 基本计算器Ⅱ
  • 282. 给表达式添加运算符

波兰式、逆波兰式介绍

我们日常用的四则运算的计算式,比如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;
}

逆波兰式运算过程

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

  • 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和nums2,做运算后将结果压入栈中;直到遍历完逆波兰式,得到的就是结果。
需要注意,num1和nums2的四则运算,加法和乘法,两个数可以交换左右顺序,但是在减法和除法中,nums1 - nums2 ≠ nums2 - nums1,需要注意第一个从栈顶取出的是nums2,之后取的是nums1。
代码如下(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();
    }
};

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;
    }
};

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

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;
    }
};

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;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1007985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【每日一题】852. 山脉数组的峰顶索引

852. 山脉数组的峰顶索引 - 力扣&#xff08;LeetCode&#xff09; 符合下列属性的数组 arr 称为 山脉数组 &#xff1a; arr.length > 3存在 i&#xff08;0 < i < arr.length - 1&#xff09;使得&#xff1a; arr[0] < arr[1] < ... arr[i-1] < arr[i] ar…

Linux重装qemu和libvirt

要重装qemu和libvirtd在CentOS上&#xff0c;请按照以下步骤操作&#xff1a; 首先&#xff0c;请确保您具有root权限或sudo访问权限。 首先&#xff0c;您需要卸载现有的qemu和libvirt软件包。使用以下命令&#xff1a; sudo yum remove qemu-kvm qemu-img libvirt在卸载完成…

MySQL基础终端命令与Python简单操作MySQL

文章目录 MySQL终端命令1. 进入mysql2. 创建数据库3. 选择数据库4. 创建数据表1. 主键约束2. 外键约束3. 非空约束4. 唯一约束5. 使用默认约束6. 设置id为自增列 5. 查看数据表6. 修改数据表1. 修改表名2. 修改表的字段类型3. 修改表的字段名4. 为表添加字段5. 删除字段6. 调整…

HTML的学习 Day01

《网页设计与制作》是web前端开发技术中静态网页中的内容&#xff0c;主要包括html、css、js中的静态内容部分&#xff0c;是专业基础课程。 随着5G时代的到来&#xff0c;人工智能与物联网结合行业的飞速发展&#xff0c;更多的互联网的崛起。这肯定就比如伴随着对移动互联网…

05-Redis高可用集群之水平扩展

上一篇&#xff1a;04-Redis哨兵高可用架构 1.概要说明 Redis3.0以后的版本虽然有了集群功能&#xff0c;提供了比之前版本的哨兵模式更高的性能与可用性&#xff0c;但是集群的水平扩展却比较麻烦&#xff0c;今天就来带大家看看redis高可用集群如何做水平扩展&#xff0c;…

Houdini 灯光插件工作流程,如何将 HDR Light Studio 与 Houdini 结合使用?

如何不额外购买硬件&#xff0c;流畅的运行Houdini&#xff1a; Houdini项目渲染慢&#xff0c;本地硬件配置不够&#xff0c;想要加速渲染&#xff0c;在不增加额外的硬件成本投入的情况下&#xff0c;最好的解决方式是使用渲云云渲染&#xff0c;在云端批量渲染&#xff0c;…

高效通过指定秒数分割视频,定制你的片段!

大家好&#xff01;在处理电脑上的视频文件时&#xff0c;如果需要从指定秒数处进行分割视频&#xff0c;传统的方法可能会显得非常繁琐和费时。为了让您能够轻松实现这一需求&#xff0c;我们为您提供了一种高效的方法&#xff0c;让您能够精准地从指定秒数处分割视频&#xf…

出现线程安全问题的原因和解决方案

文章目录 一、什么是线程安全问题二、出现线程安全问题的原因三、解决方案3.1加锁 一、什么是线程安全问题 某些代码在单线程环境下执行结果完全正确&#xff0c;但在多线程环境下执行就会出现Bug&#xff0c;这就是“线程安全问题”。 下面以一个变量n自增两次&#xff0c;每…

Linux【一】

目录 一、Linux操作系统发展历史 Unix Minix Linux 二、Linux简介 Linux是什么 Linux的版本 Linux内核版本 Linux发行版本 Linux应用领域&#xff1f; Linux注意事项 三、Linux目录 系统目录 用户目录 文件颜色 四、Linux命令行基本操作 Linux命令格式&#x…

小程序中会员如何绑定身份证信息

在小程序中&#xff0c;会员绑定身份证信息是一项重要且常见的功能&#xff0c;可以帮助商家更好地了解用户的身份信息&#xff0c;提供个性化的服务和保障用户的权益。例如生日礼物&#xff0c;还有以及医保支付等需要实名认证的场景等。下面将介绍一些小程序中会员如何绑定身…

Open Feign

Open Feign 在前面的学习中&#xff0c;我们使用了Ribbon的负载均衡功能&#xff0c;简化了远程调用时的代码&#xff1a; String user this.restTemplate.getForObject("http://spring-provider/provider/" id, String.class);如果就学到这里&#xff0c;可能以…

Zebec 生态 AMA 回顾:Nautilus 以及 $ZBC 的未来

在 9 月 7 日&#xff0c;Zebec 创始人 Sam 做客社区&#xff0c;并进行了“Nautilus Chain 以及 $ZBC 的未来”主题的 AMA 访谈。Sam 在本次访谈中对 Nautilus Chain 生态的价值捕获、Zebec 生态布局规划、可能会推出的 Nautilus Chain 治理通证 NAUT 进行了解读。本文将对本次…

基于SSM的医院在线挂号预约系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

人大女王金融硕士项目——应以长远的眼光去规划自己的人生蓝图

我们的现在的生活都是过去努力的结果&#xff0c;你的未来如何是由今后的努力决定。我们不要停滞不前&#xff0c;应从长远的眼光去规划自己的人生蓝图。你有想过在职攻读人民大学与加拿大女王大学金融硕士项目来提升自己吗&#xff1f; 着我国经济迅猛的发展&#xff0c;金融…

鸿蒙HarmonyOS应用开发初体验

最近华为发布mt60新机火了&#xff0c;作为一名移动开发程序员&#xff0c;对鸿蒙系统开发移动端就很感兴趣了。 开发工具&#xff1a;HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 下载完后按默认安装就可以了&#xff0c;界面跟AS很类似&#xff0c;之前我jdk环…

前端面试合集(二)

前端面试题合集 1.懒加载的原理及实现了解吗2.如何理解JS异步3.阐述一下 JS 的事件循环4.JS 中的计时器能做到精确计时吗&#xff1f;为什么&#xff1f; 1.懒加载的原理及实现了解吗 原理&#xff1a;当图片没有到达可视范围内时&#xff0c;图片不加载&#xff0c;当图片一旦…

【Realtek sdk-3.4.14b】RTL8197FH-VG和RTL8812F自适应认证失败问题分析及修改

WiFi自适应认证介绍 WiFi 自适应可以理解为针对WiFi的产品,当有外部干扰信号通过,WiFi产品自动停止发出信号一段时间,以达到避让的目的。 问题描述 2.4G和5G WiFi自适应认证失败,信道停止发送信号时间过长,没有在规定时间内停止发包 2.4G截图 问题分析 根据实验室描述可以…

yolov7简化网络yaml配置文件

yolov7代码结构简单&#xff0c;效果还好&#xff0c;但是动辄超过70几个模块的配置文件对于想要对网络进行魔改的朋友还是不怎么友好的&#xff0c;使用最小的tiny也有77个模块 代码的整体结构简单&#xff0c;直接将ELAN结构化写成一个类就能像yolov5一样仅仅只有20几个模块&…

[H5动画制作系列] 奔跑的豹子的四种Demo演化

资源: bg.jpg: leopard.png: 背景透明 peopard2.png 背景不透明 参考代码1: leopard.js: (function(window) {ma = function() {this.initialize();}ma._SpriteSheet = new createjs.SpriteSheet({images: ["leopard.png"], frames: [[0,0,484,207],[486,0,484,20…

H5唤醒App

H5唤醒App 在App的业务场景中&#xff0c;H5唤醒App是一个几乎必备的功能。比如你想要实现以下需求&#xff1a;当App内容通过各种途径&#xff08;短信、二维码、微信等&#xff09;触达用户&#xff0c;从浏览器或者第三方内部可以直接打开H5网页&#xff0c;由网页端交互操作…