C++题解 | 逆波兰表达式相关

news2024/11/15 3:50:46

✨个人主页: 夜 默
🎉所属专栏: C/C++相关题解
🎊每篇一句: 图片来源

  • A year from now you may wish you had started today.
    • 明年今日,你会希望此时此刻的自己已经开始行动了。

屹立不倒


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、什么是逆波兰表达式?
    • 2、150. 逆波兰表达式求值 ⭐⭐
    • 3、224. 基本计算器 ⭐⭐⭐
  • 🌆总结


🌇前言

好久没有更新题解系列博客了,今天要学习的是 逆波兰表达式,作为计算机中的重要概念,值得花时间去学习,并且其中还必须使用 容器适配器,非常适合用来练手

逆波兰表达式


🏙️正文

1、什么是逆波兰表达式?

逆波兰表达式 又称为 后缀表达式,我们从小到大学习的数学相关运算式都是 前缀表达式

  • 前缀表达式:运算符在操作数中间 (a + b) * c
  • 后缀表达式:运算符在操作数后面 a b + c *

为什么会存在这种反人类的表达式呢?

  • 因为运算式中,可能存在 ( ) 提高运算优先级的现象,计算机不像人类一样,可以一眼找到 ( ) 进行运算,只能通过特殊方法,处理运算式,使其能进行正常运算

因此,后缀表达式中是没有括号的

操作数:a、b、c
运算符:+、*

后缀表达式 的计算步骤:

  1. 从左往右,扫描表达式
  2. 获取 操作数1操作数2
  3. 再获取 运算符
  4. 进行运算:操作数1 运算符 操作数2,获取运算结果
  5. 将运算结果继续与后续 操作数运算符 进行计算

比如计算 12+3*

  • 首先计算 1 + 2 = 3
  • 其次再计算 3 * 3 = 9

最后的 9 就是最终运算结果,逆波兰表达式(后缀表达式)有效解决了计算时的优先级问题

了解 逆波兰表达式 基础知识后,就可以尝试解决相关问题了~


2、150. 逆波兰表达式求值 ⭐⭐

首先来看看第一题,也是比较简单的一题:150.逆波兰表达式求值

题目链接:150.逆波兰表达式求值

题目

题目要求:根据 逆波兰表达式 计算出结果

这里可以直接根据 逆波兰表达式(后缀表达式) 的计算步骤进行解题

解题思路:

  1. 从左往右扫描表达式(遍历即可)
  2. 遇到操作数(数字),入栈
  3. 遇到运算符,取出栈中的两个两个操作数,进行计算
  4. 将计算结果重新入栈
  5. 如此重复,直到表达式被扫描完毕

所需要的辅助工具:stack
复杂度分析:

  • 时间复杂度 O(N) 遍历一遍表达式 + 出栈入栈
  • 空间复杂度 O(N) 需要使用大小足够的栈

图解

转化为代码是这样的:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        //首先需要一个栈,用来存储操作数
        stack<int> numStack;

        //对表达式进行遍历
        for (size_t pos = 0; pos != tokens.size(); pos++)
        {
            //判断是否为操作数
            //需要注意负数,负数大小是大于1的
            if (isdigit(tokens[pos][0]) || tokens[pos].size() > 1)
            {
                //满足条件,入栈
                //注意:入栈的是整型!
                numStack.push(stoi(tokens[pos]));
            }
            else
            {
                //此时为运算符,需要进行运算
                //注意:先取的是右操作数
                int rightNum = numStack.top();
                numStack.pop();
                int leftNum = numStack.top();
                numStack.pop();

                char oper = tokens[pos][0];   //运算符
                int val = 0;    //运算结果
                switch (oper)
                {
                case '+':
                    val = leftNum + rightNum;
                    break;
                case '-':
                    val = leftNum - rightNum;
                    break;
                case '*':
                    val = leftNum * rightNum;
                    break;
                case '/':
                    val = leftNum / rightNum;
                    break;
                default:
                    break;
                }

                //将运算结果入栈
                numStack.push(val);
            }
        }

        //此时栈中的元素,就是计算结果
        return numStack.top();
    }
};

运行结果:
结果

需要注意的点:

  • isdigit 函数可以判断字符是否数字字符
  • 判断是否为操作数时,需要注意负数的情况,如 -100,可以通过判断字符串大小解决(运算符大小只为1)
  • 操作数入栈时,入的是整型,而非字符串,可以使用 stoi 函数进行转换
  • 取操作数时,先取到的是右操作数,-/ 这两个运算符需要注意运算顺序
  • 获得运算结果后,需要再次入栈

3、224. 基本计算器 ⭐⭐⭐

直接利用 后缀表达式 计算出结果很简单,但将 中缀表达式 转为 后缀表达式 就比较麻烦了

在力扣中就存在这样一道 困难题

题目链接:基本计算器

题目
题目要求:根据 中缀表达式,计算出结果

注意: 给出的 中缀表达式 中只涉及 +- 运算,但是其中又会存在很多特殊情况

特殊情况
为了使得这些特殊情况统一化,在进行表达式转换前,需要先去除其中的空格,这样就能以较为统一的视角解决问题

解题思路:

  1. 去除 中缀表达式 中的空格,方便后续进行转换
  2. 获取 逆波兰表达式(后缀表达式) (重难点)
  3. 根据 逆波兰表达式 求出结果即可

如何将 中缀表达式 转换为 后缀表达式 ?

  1. 操作数输出(非打印,而是尾插至 vector 中)
  2. 运算符:如果栈为空,直接入栈;如果栈不为空,与栈顶运算符进行优先级比较,如果比栈顶运算符优先级高,入栈,否则将栈顶运算符弹出(输出),继续比较
  3. 对于 (),认为它们的优先级都为最低,并且 ( 直接入栈,而 ) 直接弹出栈顶元素,直到遇见 (
  4. 最后将栈中的运算符全部弹出

思维导图

注意: 对于可能存在的负数,需要进行特别处理

  • - 单独出现时(左右都没有操作数),表示此时需要将右边括号中的计算结果 * -1,此时可以直接先输出元素 0,后续进行 0 - val 时,可以满足需求
  • - 仅有右边有操作数时,此时为一个单独出现的负数,输出此负数即可
  • - 左右都有操作数时,此时的 - 就是一个单纯的运算符
class Solution {
public:
    //去除空格
    int spaceRemove(string& s)
    {
        int begin = 0;
        int end = 0;
        int alphaNum = 0;
        while (end != s.size())
        {
            if (s[end] != ' ')
            {
                if (s[end] != '(' && s[end] != ')')
                    alphaNum++;
                s[begin] = s[end];
                begin++;
                end++;
            }
            else
                end++;
        }
        s.resize(begin);

        return alphaNum;
    }

    //判断是否为负数
    bool isNega(const string& s, int i)
    {
        //合法的负数:左边为 '(' 或者 左边为空
        return s[i] == '-' && (i == 0 || s[i - 1] == '(');
    }

    //获取逆波兰表达式
    void getAntiPoland(vector<string>& tokens, string s)
    {
        //借助栈,存储运算符
        stack<char> oper;

        size_t i = 0;
        while (i < s.size())
        {
            string str;

            //操作数直接输出
            if (isdigit(s[i]) || isNega(s, i))
            {
                //有可能为负数
                if (s[i] == '-')
                {
                    //特殊情况,'-' 单独出现,不配合数字
                    if (i + 1 < s.size() && !isdigit(s[i + 1]))
                    {
                        str += '0';
                        oper.push(s[i++]);
                    }
                    //普通负数的情况
                    else
                    {
                        str += s[i];
                        i++;
                    }
                }

                //处理多位数的情况
                while (isdigit(s[i]))
                {
                    str += s[i];
                    i++;
                }
            }
            else
            {
                //此时为运算符
                //栈空 或者 '(' 直接入栈
                if (oper.empty() || s[i] == '(')
                    oper.push(s[i]);
                else
                {
                    if (s[i] == ')')
                    {
                        //此时需要不断弹出
                        char tmp = oper.top();
                        oper.pop();
                        while (tmp != '(')
                        {
                            str += tmp;
                            tmp = oper.top();
                            oper.pop();
                        }
                    }
                    else if (oper.top() != '(')
                    {
                        //此时弹出并入栈
                        str = oper.top();
                        oper.pop();
                        oper.push(s[i]);
                    }
                    else
                    {
                        //此时该运算符的优先级比前面的高,直接入栈
                        oper.push(s[i]);
                    }
                }
                i++;
            }

            if (!str.empty())
                tokens.push_back(str);  //计入中缀表达式
        }

        //最后需要将栈中的运算符全部弹出
        string str;
        while (!oper.empty())
        {
            str += oper.top();
            oper.pop();
        }
        if (!str.empty())
            tokens.push_back(str);
    }

    int evalRPN(vector<string>& tokens) {
        //首先需要一个栈,用来存储操作数
        stack<int> numStack;

        //对表达式进行遍历
        for (size_t pos = 0; pos != tokens.size(); pos++)
        {
            //判断是否为操作数
            //需要注意负数,负数大小是大于1的
            if (isdigit(tokens[pos][0]) || tokens[pos].size() > 1)
            {
                //满足条件,入栈
                //注意:入栈的是整型!
                numStack.push(stoi(tokens[pos]));
            }
            else
            {
                //此时为运算符,需要进行运算
                //注意:先取的是右操作数
                int rightNum = numStack.top();
                numStack.pop();
                int leftNum = numStack.top();
                numStack.pop();

                char oper = tokens[pos][0];   //运算符
                int val = 0;    //运算结果
                switch (oper)
                {
                case '+':
                    val = leftNum + rightNum;
                    break;
                case '-':
                    val = leftNum - rightNum;
                    break;
                default:
                    break;
                }

                //将运算结果入栈
                numStack.push(val);
            }
        }

        //此时栈中的元素,就是计算结果
        return numStack.top();
    }
    int calculate(string s) {
        //核心:运算符入栈,操作数输出,根据运算符优先级进行弹出
        int alphaNum = spaceRemove(s); //为了方便后续操作,可以先去除空格
        vector<string> tokens;  //存储操作数+运算符的后缀表达式
        tokens.reserve(alphaNum);	//提前扩容,避免造成浪费
        getAntiPoland(tokens, s);   //获取逆波兰表达式(后缀表达式)
        int val = evalRPN(tokens);  //复用之前写的代码
        return val;
    }
};

结果

注:因为走的是先转换,再计算的步骤,所以整体性能会比较差,但其中加入了 逆波兰表达式 的相关知识,还是比较适合用来练手的


🌆总结

以上就是本次 逆波兰表达式 相关内容了,希望你在看完本文后能够有所收获

如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正


星辰大海

相关文章推荐

C语言题解 | 去重数组&&合并数组

C语言题解 | 消失的数字&轮转数组

C语言题解——除自身以外数组的乘积(力扣 第238题)

剑指Offer 第53题:数字在升序数组中出现的次数

C语言题解——倒置字符串(剑指Offer 第58题)

感谢支持

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

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

相关文章

改进YOLOv8 | 即插即用篇 | YOLOv8 引入 RepVGG 重参数化模块 |《RepVGG:让VGG风格的卷积神经网络再次伟大》

我们提出了一种简单但功能强大的卷积神经网络结构,该模型在推理时类似于VGG,只有33的卷积和ReLU堆叠而成,而训练时间模型具有多分支拓扑结构。训练时间和推理时间结构的这种解耦是通过结构重新参数化技术实现的,因此该模型被命名为RepVGG。在ImageNet上,RepVGG达到了超过8…

手把手教你搭建属于自己的服务器

最近总是想搭建自己的网站&#xff0c;奈何皮夹里空空如也&#xff0c;服务器也租不起&#xff0c;更别说域名了。于是我就寻思能否自己搭建个服务器&#xff0c;还不要钱呢&#xff1f; 还真行&#xff01;&#xff01;&#xff01; 经过几天的冲浪&#xff0c;我发现有两个免…

AlgoC++第七课:手写Matrix

目录 手写Matrix前言1. 明确需求2. 基本实现2.1 创建矩阵2.2 外部访问2.3 <<操作符重载 3. 矩阵运算3.1 矩阵标量运算3.2 通用矩阵乘法3.3 矩阵求逆 4. 完整示例代码总结 手写Matrix 前言 手写AI推出的全新面向AI算法的C课程 Algo C&#xff0c;链接。记录下个人学习笔记…

01 背包 (二维 )

首先是我对背包问题的理解&#xff1a; 有一个背包可以放下 n kg&#xff0c;有一些物品&#xff0c;价值和重量一一对应&#xff0c;问题是&#xff0c;需要怎样才能使背包中的价值最大&#xff1f; 不同的规则对应不同的背包问题 01背包&#xff1a;每一个物品只能被放入一次…

Docker consul的容器集群的部署|consul-template部署

Docker consul的容器集群的部署|consul-template部署 一、Consul 概述基于nginx和consul构建高可用及自动发现的Docker服务架构 二 consul实验步骤2.1 部署Consul集群 (server)2.2 Consul部署&#xff08;Client端&#xff09;2.3 consul-template部署(server)2.4 编译安装ngin…

【翻译一下官方文档】邂逅uniCloud云函数(基础篇)

我将用图文的形式&#xff0c;把市面上优质的课程加以自己的理解&#xff0c;详细的把&#xff1a;创建一个uniCloud的应用&#xff0c;其中的每一步记录出来&#xff0c;方便大家写项目中&#xff0c;做到哪一步不会了&#xff0c;可以轻松翻看文章进行查阅。&#xff08;此文…

量表题如何分析?

量表是一种测量工具&#xff0c;量表设计标准有很多&#xff0c;并且每种量表的设计都有各自的特性&#xff0c;不同量表的特性也决定了测量尺度&#xff0c;在数据分析中常用的量表为李克特量表。李克特量表1932年由美国社会心理学家李克特在当时原有总加量表的基础上进行改进…

Java8使用Stream流实现List列表简单使用

目录 1.forEach() 2.filter&#xff08;T -> boolean&#xff09; 3.findAny()和findFirst() 4.map(T -> R) 和flatMap(T -> stream) 5.distinct() 去重 6.limit(long n)和skip(long n) 7.anyMatch(T -> boolean) 8.allMatch(T -> boolean) 9.noneMat…

ASP.NET Core MVC 从入门到精通之数据库

随着技术的发展&#xff0c;ASP.NET Core MVC也推出了好长时间&#xff0c;经过不断的版本更新迭代&#xff0c;已经越来越完善&#xff0c;本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容&#xff0c;适用于初学者&#xff0c;在校毕业生&#xff0c…

ThingsBoard教程(三四):筛选规则节点 根据资产,设备,筛选,asset profile switch,device profile switch

前言 这是规则节点解析系列的第一篇,让我们先从Filter Nodes ,筛选节点类型开始。 筛选节点的作用主要是为了从筛选进入规则链的数据,根据一定的判断表达式来判断,数据向下游的那个分支流转。类似我们编程中的switch语句或if语句。 本篇主要讲解asset profile switch 与de…

每天一道算法练习题--Day13 第一章 --算法专题 --- ----------动态规划(重置版)

动态规划是一个从其他行业借鉴过来的词语。 它的大概意思先将一件事情分成若干阶段&#xff0c;然后通过阶段之间的转移达到目标。由于转移的方向通常是多个&#xff0c;因此这个时候就需要决策选择具体哪一个转移方向。 动态规划所要解决的事情通常是完成一个具体的目标&…

什么是渲染农场?我什么时候应该使用渲染农场?

网络上有关渲染农场的概念数不胜数&#xff0c;有一部分说法甚至让我们对渲染农场有了很大误解&#xff0c;究竟真正什么是渲染农场、渲染农场有多少种类型&#xff1f;我们怎么选择适合自己的渲染农场&#xff1f;这些都是各位小伙伴们近期比较关心的一些问题。 首先渲染农场是…

【C语言】基础语法7:文件操作

上一篇&#xff1a;字符串和字符处理 ❤️‍&#x1f525;前情提要❤️‍&#x1f525;   欢迎来到C语言基本语法教程   在本专栏结束后会将所有内容整理成思维导图&#xff08;结束换链接&#xff09;并免费提供给大家学习&#xff0c;希望大家纠错指正。本专栏将以基础出…

域内密码凭证获取

Volume Shadow Copy 活动目录数据库 ntds.dit&#xff1a;活动目录数据库&#xff0c;包括有关域用户、组和成员身份的 信息。它还包括域中所有用户的哈希值。 ntds.dit文件位置&#xff1a;%SystemRoot%\NTDS\NTDS.dit system文件位置&#xff1a;%SystemRoot%\System32\c…

好程序员:前端JavaScript全解析——Canvas绘制形状(下)

接着上一篇&#xff0c;好程序员继续讲解前端技术文章&#xff01; 绘制椭圆 ●canvas 也提供了绘制椭圆的 API ●语法 : 工具箱.ellipse( x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise ) ○x : 椭圆中心点的 x 轴坐标 ○y : 椭圆中心点的 y 轴坐标…

Maven详解

一、什么是Maven Maven 是⼀个项⽬构建⼯具&#xff0c;创建的项⽬只要遵循 Maven 规范&#xff08;称为Maven项目&#xff09;&#xff0c;即可使用Maven 来进行&#xff1a;管理 jar 包、编译项目&#xff0c;打包项目等功能。 为什么学习 Servlet 之前要学 Maven&#xff1…

SAM(2023)-分割万物

文章目录 摘要算法数据引擎实验7.1 零样本单点生成mask7.2 零样本边缘检测7.3. 零样本目标Proposals7.4. 零样本实例分割7.5. 零样本文本生成Mask7.6. 消融实验 讨论限制&#xff1a;结论&#xff1a; 论文: 《Segment Anything》 github: https://github.com/facebookresear…

java获取类结构信息

package com.hspedu.reflection;import org.junit.jupiter.api.Test;import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;/*** author 韩顺平* version 1.0* 演示如何通过反射获…

初级算法-回溯算法

主要记录算法和数据结构学习笔记&#xff0c;新的一年更上一层楼&#xff01; 初级算法-回溯算法 一、组合二、电话号码的字母组合三、组合总和四、组合Ⅱ五、组合Ⅲ六、分割回文串七、复原IP地址八、子集问题九、子集Ⅱ十、递增子序列十一、重新安排行程十二、全排列十三、全…

CASAIM自动化精密尺寸测量设备全尺寸检测铸件自动化检测铸件

铸造作为现代装备制造工业的基础共性技术之一&#xff0c;铸件产品既是工业制造产品&#xff0c;也是大型机械的重要组成部分&#xff0c;被广泛运用在航空航天、工业船舶、机械电子和交通运输等行业。 铸件形状复杂&#xff0c;一般的三坐标或者卡尺圆规等工具难以获取多特征…