jjzjj

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

夜 默 2024-02-09 原文

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

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


文章目录


🌇前言

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


🏙️正文

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题)

有关C++题解 | 逆波兰表达式相关的更多相关文章

  1. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  2. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  3. ruby - 正则表达式将非英文字母匹配为非单词字符 - 2

    @raw_array[i]=~/[\W]/非常简单的正则表达式。当我用一些非拉丁字母(具体来说是俄语)尝试时,条件是错误的。我能用它做什么? 最佳答案 @raw_array[i]=~/[\p{L}]/使用西里尔字符进行测试。引用:http://www.regular-expressions.info/unicode.html#prop 关于ruby-正则表达式将非英文字母匹配为非单词字符,我们在StackOverflow上找到一个类似的问题: https://

  4. ruby - 正则表达式在哪个位置失败? - 2

    我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束

  5. ruby - 有没有办法从 ruby​​ case 语句中访问表达式? - 2

    我想从then子句中访问c​​ase语句表达式,即food="cheese"casefoodwhen"dip"then"carrotsticks"when"cheese"then"#{expr}crackers"else"mayo"end在这种情况下,expr是食物的当前值(value)。在这种情况下,我知道,我可以简单地访问变量food,但是在某些情况下,该值可能无法再访问(array.shift等)。除了将expr移出到局部变量然后访问它之外,是否有直接访问caseexpr值的方法?罗亚附注我知道这个具体示例很简单,只是一个示例场景。 最佳答案

  6. ruby - 正则表达式 - 排除一个字符 - 2

    这是一个例子:s="abcd+subtext@example.com"s.match(/+[^@]*/)Result=>"+subtext"问题是,我不想在其中包含“+”。我希望结果是“潜台词”,没有+ 最佳答案 您可以在正则表达式中使用括号来创建匹配组:s="abcd+subtext@example.com"s=~/\+([^@]*)/&&$1=>"subtext" 关于ruby-正则表达式-排除一个字符,我们在StackOverflow上找到一个类似的问题:

  7. ruby - 如何遍历 Ruby 中所有正则表达式匹配的字符串? - 2

    我们有一个字符串:“”这个正则表达式://i如何从当前字符串中获取所有匹配项? 最佳答案 "".scan(//)参见scan在ruby​​-docs上 关于ruby-如何遍历Ruby中所有正则表达式匹配的字符串?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/6857852/

  8. Ruby 正则表达式匹配逗号,但忽略括号中的逗号 - 2

    我正在尝试通过正则表达式拆分参数列表。这是一个带有我的参数列表的字符串:"a=b,c=3,d=[1,3,5,7],e,f=g"我想要的是:["a=b","c=3","d=[1,3,5,7]","e","f=g"]我试过先行,但Ruby不允许使用动态范围后行,所以这行不通:/(?如何让正则表达式忽略方括号中的所有内容? 最佳答案 也许这样的东西对你有用:str.scan(/(?:\[.*?\]|[^,])+/)编辑再三考虑。简单的非贪婪匹配器在某些嵌套括号的情况下会失败。 关于Ruby正则

  9. ruby-on-rails - 在具有 ActiveRecord 条件的相关模型中按字段排序 - 2

    我正在尝试按Rails相关模型中的字段进行排序。我研究的所有解决方案都没有解决如果相关模型被另一个参数过滤?元素模型classItem相关模型:classPriority我正在使用where子句检索项目:@items=Item.where('company_id=?andapproved=?',@company.id,true).all我需要按相关表格中的“位置”列进行排序。问题在于,在优先级模型中,一个项目可能会被多家公司列出。因此,这些职位取决于他们拥有的company_id。当我显示项目时,它是针对一个公司的,按公司内的职位排序。完成此任务的正确方法是什么?感谢您的帮助。PS-我

  10. ruby - 查找重叠的正则表达式匹配项 - 2

    我想找到给定字符串中的所有匹配项,包括重叠匹配项。我怎样才能实现它?#Example"a-b-c-d".???(/\w-\w/)#=>["a-b","b-c","c-d"]expected#Solutionwithoutoverlappedresults"a-b-c-d".scan(/\w-\w/)#=>["a-b","c-d"],but"b-c"ismissing 最佳答案 在积极的前瞻中使用捕获:"a-b-c-d".scan(/(?=(\w-\w))/).flatten#=>["a-b","b-c","c-d"]参见Rubyde

随机推荐