【面试经典150 | 栈】逆波兰表达式求值

news2024/11/24 8:01:51

文章目录

  • 写在前面
  • Tag
  • 题目来源
  • 题目解读
  • 解题思路
    • 方法一:栈
    • 方法二:使用数组模拟栈
  • 知识点拨
    • 两个概念
    • 中缀表达式转后缀表达式
    • 后缀表达式计算四则运算表达式
    • 例题
  • 写在最后

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【逆波兰】【后缀表达式】【栈】


题目来源

150. 逆波兰表达式求值


题目解读

计算逆波兰表达式表示的算术表达式的结果。


解题思路

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

以下将介绍两种方法来解决本题,本质上都是使用栈操作,方法一中直接使用栈,方法二我们利用数组模拟栈。

方法一:栈

定义一个栈 stk,栈中存放 int 整型数字。遍历字符串数组:

  • 遇到数字字符将数字字符转化成 int 整型数字存放在栈中;
  • 遇到运算符,就将栈顶两个数字出栈,执行相应的运算符操作。

我们在遍历字符串数组的时候,需要将字符型数字转化成整型数字,在 C++ 中有两种方法:

  • stoi():将字符串型数字转化成整型数字;
  • atoi():将字符型数字转化成整型数字。

实现代码

class Solution {
public:
    bool isNumber(string& str) {
        return !(str == "+" || str == "-" || str == "*" || str == "/");
    }

    int evalRPN(vector<string>& tokens) {
        stack<int> stk;
        for (auto token : tokens) {
            if (isNumber(token)) {
                stk.push(atoi(token.c_str()));
            }
            else {
                int num2 = stk.top(); stk.pop();
                int num1 = stk.top(); stk.pop();
                switch(token[0]) {
                    case '+':
                        stk.push(num1 + num2);
                        break;
                    case '-':
                        stk.push(num1 - num2);
                        break;
                    case '*':
                        stk.push(num1 * num2);
                        break;
                    case '/':
                        stk.push(num1 / num2);
                        break;
                }
            }
        }
        return stk.top();
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n) n n n 为字符串数组的长度。

空间复杂度: O ( n ) O(n) O(n)

方法二:使用数组模拟栈

我们可以使用数组来模拟栈。本方法参考自

使用数组的话需要首先定义数组的长度。对于长度为 n n n 的逆波兰表达式, n n n 一定为奇数,表达式中的操作数的个数一定比运算符多一个,即操作数有 n + 1 2 \frac{n+1}{2} 2n+1 个,运算符有 n − 1 2 \frac{n-1}{2} 2n1 个。我们遍历逆波兰表达式:

  • 如果遇到操作数,则将操作数入栈,因此栈内元素增加 1 个;
  • 如果遇到运算符,则将两个操作数出栈,然后将一个新操作数入栈,因此栈内元素先减少 2 个再增加 1 个,结果是栈内元素减少 1 个。

具体实现中,我们创建一个数组 stack 模拟栈,数组下标 0 的位置对应栈底,定义 idx 对应栈顶元素的下标,初始时栈为空,idx = -1。遇到操作数和运算符时,进行如下操作:

  • 如果遇到操作符,则 idx 加一,然后将操作数赋值给 stack[idx]
  • 如果遇到运算符,则 idx 减一,此时的 stack[idx]stack[idx+1] 分别是左操作数和右操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数赋给 stack[idx]

遍历完整个逆波兰表达式后,栈中只剩下一个元素,表现在数组 stack 中就是 idx = 0,最后返回 stack[idx] 即为逆波兰表达式的值。

实现代码

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        int n = tokens.size();
        vector<int> stack((n + 1) / 2);
        int idx = -1;
        for (auto token : tokens) {
            if (token.size() > 1 || isdigit(token[0])) {
                ++idx;
                stack[idx] = atoi(token.c_str());
            }
            else {
                switch(token[0]) {
                    case '+':
                        --idx;
                        stack[idx] += stack[idx + 1];
                        break;
                    case '-':
                        --idx;
                        stack[idx] -= stack[idx + 1];
                        break;
                    case '*':
                        --idx;
                        stack[idx] *= stack[idx + 1];
                        break;
                    case '/':
                        --idx;
                        stack[idx] /= stack[idx + 1];
                        break;
                }
            }        
        }
        return stack[idx];
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 tokens 的长度。需要遍历数组 tokens 一次,计算逆波兰表达式的值。

空间复杂度: O ( n ) O(n) O(n)。需要创建长度为 n + 1 2 \frac{n+1}{2} 2n+1 的数组模拟栈操作。


知识点拨

我们来看一下如何使用栈来计算四则运算表达式,通常利用栈来计算四则运算表达式需要先将四则元素表达式即中缀表达式转化成后缀表达式,然后利用后缀表达式来求计算结果。

我们先来看一个中缀表达式和后缀表达式的概念。

两个概念

  • 中缀表达式,中缀表达式就是我们平常所说的 “四则运算表达式” ,形象的说明就是运算符号分布在两个数字中间。
  • 后缀表达式,后缀表达式就是,形如 “9 3 1 - 3 * + 10 2 / +” 这样形式的,运算符号都出现在运算数字之后的形式。

中缀表达式转后缀表达式

在明确了一些基本的概念之后,我们就可以来看看如何将中缀表达式转后缀表达式。

维护一个栈作为辅助工具,维护一个字符串数组作为输出的后缀表达式。从左到右依次遍历中缀表达式中的字符:

  • 若是数字就是直接输出到数组中,成为后缀表达式的一部分;
  • 若是符号则进行如下判断:
    • 当前符号是右括号则栈顶元素依次出栈;
    • 当前符号优先级低于栈顶符号,或者一致,则出栈;
    • 当前符号入栈。
  • 重复上述,过程直至最后输出后缀表达式。

后缀表达式计算四则运算表达式

这个问题,在题目中已经详细讲解过了,这里就不再解释了。

例题

用栈来计算标准四则运算表达式 ( 12 − 3 ) / 3 + 2 ∗ 3 (12-3) / 3+2 *3 (123)/3+23 的值。

一般来说,不会让你求一个中缀表达式的后缀表达式,更多的考察形式是给你一个后缀表达式让你对应对应中缀表达式的值。

接下来将以图解的形式来看一下中缀转后缀和利用后缀计算表达式值的过程。

中缀表达式转后缀表达式

中缀转后缀用栈示意图

1 初始化一个空栈,用来运算符号进出栈使用

2 开始遍历中缀表达式,第一个符号是左括号直接入栈

3 当前字符是 “12” 是数字,直接输出

4 第三个字符是 ”-“ 是运算符号,入栈

5 第四个字符是运算数字,直接输出

6 接着的字符是右括号,匹配前面的左括号,因此将栈顶依次出栈直至遇到左括号为止,将出栈的符号依次输出

7 栈空了, ÷ \div ÷ 直接入栈

8 遇到运算数字,直接输出

9 当前符号是运算符号 “+” ,优先级低于栈顶元素, ÷ \div ÷ 出栈并输出,”+“ 入栈

10 遇到数字,直接输出

11 当前符号是运算符号 × \times × ,优先级并不比栈顶符号优先级低或者等于,符号直接入栈

12 遇到数字直接入栈,中缀表达式遍历完毕,栈内符号依次出栈,最终输出后缀表达式 12   3 − 3   ÷   2   3   ×   + 12 \space 3 - 3 \space \div \space 2 \space 3 \space \times \space + 12 33 ÷ 2 3 × + .

利用后缀表达式计算

后缀计算用栈示意图

1 维护一个空栈,用来存放运算数字

2 遇到数字直接入栈

3 遇到运算符号 − - ,出栈得到减数 3 ,再次出栈得到被减数 12 ,两数相减得到 9,入栈

4 遇到数字直接入栈

5 遇到运算符号 ÷ \div ÷ ,出栈得到除数 3 ,再次出栈得到被除数 9 ,两数相除得到 3,入栈

6 遇到数字直接入栈

7 遇到运算符号 × \times × ,出栈得到乘数 3 ,再次出栈得到乘数 2 ,两数乘得到 6,入栈

8 遇到运算符号 + + + ,所以将 6 与 3 出栈并相加,得到 9,并入栈

结果为 9 出栈,栈为空。


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。

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

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

相关文章

时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测

时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测 目录 时序预测 | Python实现ARIMA-LSTM差分自回归移动平均模型结合长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 时序预测 | Python实现ARIM…

闭包的理解?一般使用场景

是什么 一个函数和对其周围状态(词法环境)的引用捆绑在一起(函数被引用包围)&#xff0c;这样的组合我们称之为闭包 也就是说&#xff0c;闭包让你可以在一个内层函数中访问到其外层函数的作用域 &#x1f356;在 JavaScript中&#xff0c;每当创建一个函数&#xff0c;闭包…

Java SE 学习笔记(十七)—— 单元测试、反射

目录 1 单元测试1.1 单元测试概述1.2 单元测试快速入门1.3 JUnit 常用注解 2 反射2.1 反射概述2.2 获取类对象2.3 获取构造器对象2.4 获取成员变量对象2.5 获取常用方法对象2.6 反射的作用2.6.1 绕过编译阶段为集合添加数据2.6.2 通用框架的底层原理 1 单元测试 1.1 单元测试概…

Java字节码创建对象指令

接上节。 示例代码&#xff1a; package com.lkl.jvmDemo;public class HelloByteCode {public static void main(String[] args) {HelloByteCode obj new HelloByteCode();} }查看字节码清单javap -c -verbose HelloByteCode Classfile /XXX/com/lkl/jvmDemo/HelloByteCod…

【电路笔记】-交流电阻和阻抗

交流电阻和阻抗 文章目录 交流电阻和阻抗1、概述&#xff1a;电阻率2、交流状态与直流状态近似性3、交流状态与直流状态的差异性3.1 趋肤效应(The Skin Effect)3.2 靠近效应&#xff08;The Proximity Effect&#xff09; 4、总结 电阻是一种特性&#xff0c;用于表征当电压差施…

2023最流行的自动化测试工具有哪些?

一&#xff1a;前言 随着测试工程师技能和工资待遇的提升&#xff0c;甚至有一部分的开发人员开始转入测试岗位&#xff0c;跨入自动化领域的测试攻城狮越来越多。在自动化测试领域&#xff0c;自动化工具肯定占据了核心的位置。 本文总结了常用的测试自动化工具和框架&#x…

【机器学习可解释性】5.SHAP值的高级使用

机器学习可解释性 1.模型洞察的价值2.特征重要性排列3.部分依赖图4.SHAP 值5.SHAP值的高级使用 正文 汇总SHAP值以获得更详细的模型解释 总体回顾 我们从学习排列重要性和部分依赖图开始&#xff0c;以显示学习后的模型的内容。 然后我们学习了SHAP值来分解单个预测的组成部…

汇编语言-div指令溢出问题

汇编语言-div指令溢出问题 8086CPU中被除数保存在ax(16位)或ax和dx&#xff08;32位&#xff09;中&#xff0c;如果被除数为16位&#xff0c;进行除法运算时al保存商&#xff0c;ah保存余数。如果被除数为32位时&#xff0c;进行除法运算时&#xff0c;ax保存商&#xff0c;d…

从最简单基本开始 or 把问题复杂化还自诩为“设计了一个可扩展的系统”?

文章目录 Intro程序员“把问题复杂化”的职业病如何抉择 Intro 刚才看了一段关于在苹果系统中使用numbers表格软件制作记账本的视频教程&#xff1a;当 Excel 交给苹果来设计会变成…&#xff1f;#Numbers 新手教学&#xff0c;以下为最终界面效果&#xff1a; 有些触动&…

网络原理续

传输层的协议也并非就只有UDP和TCP 就拿王者荣耀这个游戏来说 是否需要可靠性是否需要高效率 那是使用TCP还是UDP呢? 当然是都不用, 除了这两个协议外, 有的传输层协议就是为游戏场景量身打造的. 比如说以KCP为代表的一系列协议. 网络层 地址管理路由选择 网络层的代表:…

【FreeRTOS】

FreeRTOS 一、FreeRTOS任务创建和删除1.1 动态方式1.2 静态方式 二、任务挂起和恢复三、中断管理四、临界区保护及调度器的挂起和恢复五、列表项的插入和删除六、时间片调度七、任务状态查询API函数介绍7.1 任务状态查询API7.2 任务运行时间统计API 八、时间管理九、队列十、信…

SpringBoot小项目——简单的小区物业后台管理系统 认证鉴权 用户-角色模型 AOP切面日志 全局异常【源码】

目录 引出一、应用到的技术栈Spring、Spring MVC、Spring Boot基础SpringBoot进阶、SpringMVC原理、AOP切面MyBatis 数据库相关JavaWeb基础&#xff1a;Session等前端Vue、JavaScript、Bootstrap 二、后台管理系统的功能登录功能1.用户名密码登录2.验证码的登录 报修业务的处理…

【多线程面试题十】、说一说notify()、notifyAll()的区别

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;说一说notify()、notify…

C++哈希表:一种快速查找和插入的方法

文章目录 1、前言2、unordered系列关联式容器2.1、 unordered_map2.1.1、unordered_map的文档介绍2.1.2、unordered_map的接口说明 2.2、 unordered_set 3. 底层结构3.1 哈希概念3.2 哈希冲突2.3 哈希函数2.4 哈希冲突解决2.4.1 闭散列2.4.2 开散列 4. 模拟实现4.1 哈希表的改造…

论文速递 TMC 2023 | RoSeFi: 一种利用商用WiFi设备进行稳健的久坐行为监测系统

注1:本文系“最新论文速览”系列之一,致力于简洁清晰地介绍、解读最新的顶会/顶刊论文 TMC 2023 | RoSeFi: 一种利用商用WiFi设备进行稳健的久坐行为监测系统 原文链接:https://ieeexplore.ieee.org/abstract/document/10269067 本文提出了一种稳健的久坐行为监测系统RoSeFi。…

ITSource 分享 第5期【校园信息墙系统】

项目介绍 本期给大家介绍一个 校园信息墙 系统&#xff0c;可以发布信息&#xff0c;表白墙&#xff0c;分享墙&#xff0c;校园二手买卖&#xff0c;咨询分享等墙信息。整个项目还是比较系统的&#xff0c;分为服务端&#xff0c;管理后台&#xff0c;用户Web端&#xff0c;小…

SHCTF 山河CTF Reverse方向[Week1]全WP 详解

文章目录 [WEEK1]ez_asm[WEEK1]easy_re[WEEK1]seed[WEEK1]signin[WEEK1]easy_math[WEEK1]ez_apk [WEEK1]ez_asm 从上往下读&#xff0c;第一处是xor 1Eh&#xff0c;第二处是sub 0Ah&#xff1b;逆向一下先加0A后异或1E 写个EXP data "nhuo[M7mc7uhc$7midgbTf7$7%#ubf7 …

Go命令行参数操作:os.Args、flag包

Go命令行参数操作&#xff1a;os.Args、flag包 最近在写项目时&#xff0c;需要用到命令行传入的参数&#xff0c;正好借此机会整理一下。 1 os.Args&#xff1a;程序运行时&#xff0c;携带的参数&#xff08;包含exe本身&#xff09; package mainimport ("fmt"&q…

ZYNQ连载02-开发环境

ZYNQ连载02-开发环境 1. 官方文档 ZYNQ开发使用的软件为Vivado/Vitis/PetaLinux,软件体积比较大&#xff0c;硬盘保留100G以上的空间&#xff0c;赛灵思提供详细的文档&#xff0c;链接如下&#xff1a; ZYNQ文档 2. Vivido和Vitis安装 赛灵思统一安装程序 3. PetaLinux安装…

OV-VG: A Benchmark for Open-Vocabulary Visual Grounding

OV-VG: A Benchmark for Open-Vocabulary Visual Grounding 一、Abstract 写在前面 又是一周周末&#xff0c;光调代码去了&#xff0c;都没时间看论文了&#xff0c;汗。   这是一篇关于开放词汇定位的文章&#xff0c;也是近两年的新坑&#xff0c;但是资源也是需要不少。 …