力扣习题:基本计算器

        本片内容我们将针对于一个力扣中的一道很经典的习题:基本计算器。

        这道题目十分经典,在很多大厂的面试题中都有出现过

        因此我们将进一步来学习

        该题目代码已经上传作者的个人gitee:CPP 学习代码库: C++代码库新库,旧有C++仓库满员了喜欢请支持一下谢谢。

题目:

        实现的思路:

        计算器顾名思义就是我们给一个计算表达式让其把计算结果求解出来。日常生活中我们使用的都是中缀表达式。也就是运算符在中间、运算数在两边的表达式,比如1-(3-2)*5。但是中缀表达式涉及到优先级的问题,对于计算器而言没办法直接解决这个问题。

        其中的一种设计思路是可以将中缀表达式转换为后缀表达式。将操作符放到操作数后面,运算符运算的顺序就是运算符出现的顺序。

        后缀表达式/逆波兰表达式的计算

        逆波兰表达式的讲解可以参考以下资料:【数据结构】你知道波兰表达式和逆波兰表达式吗?我才知道原来栈在表达式求值中还能这样使用……-腾讯云开发者社区-腾讯云

        逆波兰表达式在力扣上也有习题:150. 逆波兰表达式求值 - 力扣(LeetCode)

        

        

        思路:

        后缀表达式因为已经确定好优先级,运算符方式非常简单,就是遇到运算符的时候取前面两个运算数进行运算。因为经过中缀表达式后后缀表达式已经确定好了。

        建立一个栈存储数据,读取后缀表达式,遇到运算数入栈,遇到运算符出栈顶两个数据运算后的结果作为数据入栈参与下一次运算。读取结束后,栈内的结果就是表达式的值。        

       因此实际上后缀表达式进行四则运算的结果为:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(auto& str:tokens){if(str=="+"||str=="-"||str=="*"||str=="/"){//遇到运算符要出栈两个运算数然后运算后入栈int right=st.top();st.pop();int left=st.top();st.pop();switch(str[0]){case '+':st.push(left+right);break;case '-':st.push(left-right);break;case '*':st.push(left*right);break;case '/':st.push(left/right);break;default:break;}}else{//运算数入栈st.push(stoi(str));}}        return st.top();}
};

        中缀表达式转后缀表达式

        依次读取计算表达式上的数值,直到遇到运算数直接输出。

        建立一个栈存储运算符,利用栈后进先出的特性遇到后米娜的运算符,出栈里面前面的运算符进行比较,确定优先级。

        遇到运算符,如果栈为空或者栈不为空且当前运算符比栈顶运算符高的时候,则当前运算符入栈。因为如果栈里存储的是前一个运算符,当前运算符比前一个高,则说明前一个运算符不能运算、当前运算符也不能运算,因为后面可能有优先级更高的。

        遇到运算符,如果栈不为空且当前运算符比栈顶运算符低或者相等的时候,说明栈顶的运算符可以运算了,则输出栈顶运算符,当前运算符继续走前面遇到运算符的逻辑。

        如果遇到(),则把()的计算表达式当成一个子表达式,和上面的思路类似,进行递归处理子表达式,处理转换之后的后缀表达式加在前面表达式的后面即可。

        计算表达式或()中子表达式结束值,输出栈中所有的运算符

        代码实现

//比较运算符优先级大小
//方案一:
int operatorpriority(char ch)
{struct opPD{char _op;//运算符int _pd;//优先级};static const opPD opPrecedence[] = { {'+',1},{'-',1},{'*',2},{'/',2}};for (auto& str: opPrecedence){if (str._op == ch){return str._pd;}}assert(false);return -1;}
//方案二:
int operatorprecdence(char ch)
{map<char, int> mp = { {'+',1},{'-',1},{'*',2},{'/',2} };for (auto& str : mp){if (str.first == ch){return str.second;}}assert(false);return -1;
}
//中缀表达式转后缀表达式
//i是递归的下标
//vector<string>存储结果
void toPRN(const string& s,size_t& i,vector<string>&v)
{stack<char> st;//遍历中缀表达式while (i<s.size()){//判断是否为数字if (isdigit(s[i])){//如果是操作数、运算数就直接输出string num;while (i < s.size()&&isdigit(s[i])){num += s[i];++i;}v.push_back(num);}//开始递归else if (s[i]=='('){//子表达式,递归处理++i;toPRN(s, i, v);}//结束递归else if (s[i] == ')'){//子表达式结束//弹出栈顶剩余运算符while (!st.empty()){v.push_back(string(1,st.top()));st.pop();}++i;return;}else{//如果是运算符//栈为空或者栈运算符优先级高if (st.empty()|| operatorprecdence(s[i])> operatorprecdence(st.top())){st.push(s[i]);++i;}else{//栈不为空且栈顶运算符优先级相等或低char op = st.top();st.pop();//用string 构造v.push_back(string(1,op));}}//表达式结束//弹出栈顶剩余运算符while (!st.empty()){v.push_back(string(1, st.top()));st.pop();}}
}

        

基本计算器代码实现

        

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<map>
#include<string>
#include<stack>
#include<vector>
#include<assert.h>
using namespace std;
class Solution 
{
public://map<char, int> _operatorPrecedence = { { '+', 1 }, { '-', 1 }, { '*', 2 }, { '/', 2 } };int operatorPrecedence(char ch){struct opPD{char _op;int _pd;};static opPD arr[] = { {'+', 1},{'-', 1},{'*', 2},{'/', 2} };for (auto& e : arr){if (e._op == ch){return e._pd;}}assert(false);return -1;}void toRPN(const string& s, size_t& i, vector<string>& v){stack<char> st;while (i < s.size()){if (isdigit(s[i])){// 运算数输出string num;while (i < s.size() && isdigit(s[i])){num += s[i];++i;}v.push_back(num);}else{if (s[i] == '('){// 递归⽅式处理括号中的⼦表达式++i;toRPN(s, i, v);}else if (s[i] == ')'){++i;// 栈中的运算符全部输出while (!st.empty()){v.push_back(string(1, st.top()));st.pop();}//结束递归return;}else{//运算符// 1、如果栈为空或者栈不为空且当前运算符⽐栈顶运算符优先级⾼,则当 前运算符⼊栈// 2、如果栈不为为空且⽐栈顶运算符优先级低或相等,说明栈顶的运算符可以运算了,// 输出栈顶运算符,当前运算符继续⾛前⾯遇到运算符的逻辑if (st.empty() || operatorPrecedence(s[i]) >operatorPrecedence(st.top())){st.push(s[i]);++i;}else{v.push_back(string(1, st.top()));st.pop();}}}}//栈中的运算符全部输出while (!st.empty()){v.push_back(string(1, st.top()));st.pop();}}int evalRPN(const vector<string>& tokens) {stack<int> s;for (size_t i = 0; i < tokens.size(); ++i){const string& str = tokens[i];// str为数字if (!("+" == str || "-" == str || "*" == str || "/" == str)){s.push(stoi(str));}else{// str为操作符int right = s.top();s.pop();int left = s.top();s.pop();switch (str[0]){case '+':s.push(left + right);break;case '-':s.push(left - right);break;case '*':s.push(left * right);break;case '/':s.push(left / right);break;}}}return s.top();}int calculate(string s){// 1、去除所有空格,否则下⾯的⼀些逻辑没办法处理std::string news;news.reserve(s.size());for (auto ch : s){if (ch != ' ')news += ch;}s.swap(news);news.clear();// 2、将所有的负数 - x转换为0 - xfor (size_t i = 0; i < s.size(); ++i){if (s[i] == '-' && (i == 0 || (!isdigit(s[i - 1]) && s[i - 1] !=')')))news += "0-";elsenews += s[i];}// 中缀表达式转成后缀表达式size_t i = 0;vector<string> v;toRPN(news, i, v);// 后缀表达式进⾏运算return evalRPN(v);}
};

(科普)前缀/中缀/后缀表达式

核心概念:操作符的位置

这些表达式类型的核心区别在于操作符相对于操作数的位置。

  1. 操作数 (Operand): 表示参与运算的值(如数字、变量)。例如 a5x

  2. 操作符 (Operator): 表示要执行的运算(如 +-*/)。例如 +*


1. 中缀表达式 (Infix Notation)

  • 定义: 这是我们日常生活中最熟悉、最常用的数学表达式书写方式。操作符位于两个操作数之间

  • 特点:

    • 符合人类的阅读和书写习惯。

    • 需要括号操作符优先级(如 * 和 / 比 + 和 - 优先)来确定运算顺序。这是它最大的复杂性来源。

    • 对计算机不友好,因为计算机需要解析括号和优先级才能确定计算顺序。

  • 示例:

    • A + B

    • (A + B) * C

    • A + B * C - D / E

    • ( (A + B) * (C - D) ) / E


2. 后缀表达式 (Postfix Notation) / 逆波兰表达式 (Reverse Polish Notation - RPN)

  • 定义: 操作符位于其对应的操作数之后

  • 特点:

    • 也称为逆波兰表达式 (RPN)。这是波兰数学家的发明,后缀是“逆”着前缀的顺序来的。

    • 完全不需要括号!表达式的结构本身就隐含了唯一的运算顺序。

    • 对计算机极其友好。使用一个简单的栈 (Stack) 数据结构就能高效地计算出结果,无需考虑优先级和括号。

    • 计算过程是从左到右线性扫描。

  • 计算规则 (使用栈):

    1. 从左到右扫描表达式。

    2. 遇到操作数:将其压入栈中。

    3. 遇到操作符

      • 从栈顶弹出所需数量的操作数(二元操作符弹出两个,一元操作符弹出一个)。

      • 用该操作符对弹出的操作数进行运算。

      • 将运算结果压回栈中。

    4. 重复步骤 1-3,直到表达式结束。

    5. 栈中最后剩下的唯一元素就是整个表达式的计算结果。

  • 示例 (与中缀对应):

    • 中缀 A + B -> 后缀 A B +

    • 中缀 (A + B) * C -> 后缀 A B + C *

    • 中缀 A + B * C -> 后缀 A B C * + (注意:* 优先级高,先结合 B 和 C)

    • 中缀 A * B + C -> 后缀 A B * C +

    • 中缀 (A + B) * (C - D) -> 后缀 A B + C D - *

    • 中缀 ( (A + B) * (C - D) ) / E -> 后缀 A B + C D - * E /

  • 为什么叫“逆波兰”? 它是“波兰表达式”(前缀表达式)的“逆序”版本(操作符在操作数后面 vs. 操作符在操作数前面)。


3. 前缀表达式 (Prefix Notation) / 波兰表达式 (Polish Notation - PN)

  • 定义: 操作符位于其对应的操作数之前

  • 特点:

    • 也称为波兰表达式 (PN)

    • 和 RPN 一样,完全不需要括号!表达式的结构隐含了唯一的运算顺序。

    • 同样对计算机友好,也可以用栈高效计算(扫描方向不同)。

    • 计算过程需要从右到左扫描。

  • 计算规则 (使用栈):

    1. 从右到左扫描表达式。

    2. 遇到操作数:将其压入栈中。

    3. 遇到操作符

      • 从栈顶弹出所需数量的操作数(二元操作符弹出两个,一元操作符弹出一个)。

      • 用该操作符对弹出的操作数进行运算。

      • 将运算结果压回栈中。

    4. 重复步骤 1-3,直到表达式开始。

    5. 栈中最后剩下的唯一元素就是整个表达式的计算结果。

  • 示例 (与中缀对应):

    • 中缀 A + B -> 前缀 + A B

    • 中缀 (A + B) * C -> 前缀 * + A B C

    • 中缀 A + B * C -> 前缀 + A * B C (注意:* 优先级高,先结合 B 和 C)

    • 中缀 A * B + C -> 前缀 + * A B C

    • 中缀 (A + B) * (C - D) -> 前缀 * + A B - C D

    • 中缀 ( (A + B) * (C - D) ) / E -> 前缀 / * + A B - C D E

  • 为什么叫“波兰”? 由波兰数学家扬·武卡谢维奇(Jan Łukasiewicz)在 1920 年代发明而得名。


总结对比表

特性中缀表达式 (Infix)后缀表达式 (Postfix) / 逆波兰 (RPN)前缀表达式 (Prefix) / 波兰 (PN)
操作符位置操作数之间操作数之后操作数之前
括号需求必需 (消除歧义)不需要不需要
优先级需求必需 (确定运算顺序)不需要 (顺序隐含)不需要 (顺序隐含)
人类可读性最好 (最自然)较差较差
计算机友好度最差 (解析复杂)很好 (栈,左->右扫描)很好 (栈,右->左扫描)
计算扫描方向无固定方向 (需解析)左 -> 右右 -> 左
别名-逆波兰表达式 (RPN)波兰表达式 (PN)
示例(A + B) * C - D / EA B + C * D E / -- * + A B C / D E
核心优势符合直觉无括号,易计算无括号,易计算
核心劣势需括号和优先级,难解析对人类不直观对人类不直观

        
 

        本期内容就到这里了,喜欢请点个赞谢谢

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

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

相关文章

Element用法---Loading 加载

仅供参考 文章目录一、加载动画二、Loading 组件1、指令调用 Loading2、服务调用 Loading一、加载动画 当我们打开某个页面时&#xff0c;如果需要加载的数据很多或者网络很差&#xff0c;页面加载就会非常缓慢&#xff0c;中间可能会很长时间显示空白&#xff0c;那么就需要加…

飞算AI 3.2.0实战评测:10分钟搭建企业级RBAC权限系统

飞算AI 3.2.0实战评测&#xff1a;10分钟搭建企业级RBAC权限系统 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都…

事务的四大特性

事务&#xff08;Transaction&#xff09;是数据库管理系统&#xff08;DBMS&#xff09;中用于保证数据操作正确性和一致性的核心机制。事务的特性通常用 ACID 四个字母概括&#xff0c;分别代表 原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&…

WIN11系统下Open3D 0.19.0支持GPU的python版本

前往Open 3D官网下载https://github.com/isl-org/Open3D下载对应版本的源码。 根据官方手册利用cmake进行编译&安装&#xff0c;其中需要修改一些代码适应于win 11系统&#xff0c;编译时间较长需要耐心等待。最后&#xff0c;安装结果如下图&#xff0c;搞了四天&#xff…

ICCV 2025 | 4相机干掉480机位?CMU MonoFusion高斯泼溅重构4D人体!

​​​​ 近日&#xff0c;卡内基梅隆大学&#xff08;Carnegie Mellon University&#xff09;的研究团队在动态场景重建领域取得重要进展。其发表于ICCV 2025的论文《MonoFusion: Sparse-View 4D Reconstruction via Monocular Fusion》提出创新方法MonoFusion 。该方法突破常…

ADB 无线调试连接(Windows + WSL 环境)

gradle wrapper --gradle-version 8.4 Windows WSL 成功连接 Android 设备&#xff08;用于 ./gradlew installDebug&#xff09;的完整过程总结&#xff1a;✅ ADB 无线调试连接过程&#xff08;Windows WSL 环境&#xff09; &#x1f4cc; 目标&#xff1a;从 WSL 中通过 …

【.net core】【wetercloud】处理前端项目免登陆,且从前端项目跳转至系统内时的问题

1.前端项目访问后台内容时免登陆&#xff08;一般用于后台接口需要校验登陆时&#xff09;处理思路&#xff1a;将后台用户的登陆校验令牌信息在用户登录后添加至前端项目访问地址的参数列表中&#xff0c;如&#xff1a;https://yourdomain/Home/Index#/https://yourdomain/vi…

设备 AI 知识库,管理效率新飞跃

在设备管理领域&#xff0c;高效解决设备故障、合理规划维护工作对企业生产运营至关重要。易点易动设备管理系统新推出的设备 AI 知识库&#xff0c;为提升管理效率带来了新契机。设备 AI 知识库集成先进的人工智能技术&#xff0c;是设备管理领域的创新应用。易点易动设备管理…

C#绘制斐波那契螺旋

Fabonacci 数列&#xff0c;也就是”兔子数列“&#xff0c; 如果第一项为0的话&#xff0c;就是&#xff0c; 0&#xff0c;1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13&#xff0c;21&#xff0c;34&#xff0c;55&#xff0c;89……

JavaScript 任务 - clearTimeout 函数与 clearInterval 函数

clearTimeout 函数 1、基本介绍 clearTimeout 函数用于取消先前通过 setTimeout 函数设置的定时器 clearTimeout(【timeoutID】)参数说明timeoutID要取消的定时器的标识符&#xff0c;这个 ID 是由 setTimeout 函数返回的2、演示 let timeoutId1 setTimeout(() > {console.…

在 CentOS 7 中使用 systemd 创建自定义服务

systemd 创建自定义服务简述创建自定义服务步骤文件覆盖优先级创建服务流程在 /etc/systemd/system/ 目录下创建 .service 文件&#xff08;需 root 权限&#xff09;&#xff1a;编写服务配置模板Systemd 服务文件三大区块详解[Unit] 区块 - 服务元数据与依赖[Service] 区块 -…

【QT】printsupport库远程实现打印机打印

【QT】printsupport库远程实现打印机打印 前言 思路 实现 当前所有可用打印机浏览 打印预览 打印输出 手动选择打印 自动打印 防呆补充 库打包 前言 在打印机的通讯控制方式中,有USB、网口、串口、WIFI等,针对局域网环境下,用自研软件控制打印机打印的应用场景,针对自研软…

LT3045EDD#TRPBF ADI亚德诺 超低噪声LDO稳压器 电子元器件IC

LT3045EDD#TRPBF ADI 超低噪声LDO稳压器专业解析1. 产品技术档案LT3045EDD#TRPBF是ADI&#xff08;Analog Devices Inc.&#xff09;推出的超低噪声/超高PSRR线性稳压器&#xff0c;采用DFN-12 (3x3mm)封装&#xff0c;以其0.8μVRMS超低噪声和79dB超高频PSRR成为精密电源设计。…

易语言模拟真人鼠标轨迹算法 - 非贝塞尔曲线

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟人…

Spring Boot 3 数据源连接信息存储方法

在Spring Boot 3中&#xff0c;数据源连接信息的存储方式主要有以下几种&#xff0c;根据安全性和环境需求选择合适的方式&#xff1a; 1. 配置文件&#xff08;推荐基础方式&#xff09; 位置&#xff1a;src/main/resources/application.properties 或 application.yml 示例…

按键序列常用示例

按键序列常用示例 按键编码 基础按键对应编码 A-Z 原字符即可 KeyCodeSHIFTCTRL^ALT% 其他按键 KeyCodeBACKSPACE{BACKSPACE}, {BS}, or {BKSP}BREAK{BREAK}CAPS LOCK{CAPSLOCK}DEL or DELETE{DELETE} or {DEL}DOWN ARROW{DOWN}END{END}ENTER{ENTER} or ~ESC{ESC}HELP{HEL…

【LeetCode Solutions】LeetCode 热题 100 题解(36 ~ 40)

CONTENTS二叉树 - LeetCode 94. 二叉树的中序遍历&#xff08;简单&#xff09;二叉树 - LeetCode 104. 二叉树的最大深度&#xff08;简单&#xff09;二叉树 - LeetCode 226. 翻转二叉树&#xff08;简单&#xff09;二叉树 - LeetCode 101. 对称二叉树&#xff08;简单&…

数据处理分析环境搭建+Numpy使用教程

环境搭建 数据分析常用开源库 Numpy NumPy(Numerical Python) 是 Python 语言的一个扩展程序库。是一个运行速度非常快的数学库&#xff0c;主要用于数组计算包含&#xff1a; 一个强大的N维数组对象 ndarray广播功能函数整合 C/C/Fortran 代码的工具线性代数、傅里叶变换、随机…

实战多屏Wallpaper壁纸显示及出现黑屏问题bug分析-学员作业

背景&#xff1a; 在大家看了上一篇google官方对于多屏壁纸这块的介绍后 安卓Wallpaper壁纸部分对多屏的支持-Google官方文档介绍 可能还是对于壁纸支持多屏这块没有相关的实战性的认知&#xff0c;所以本文就开始带大家来进行部分解读和实战。 壁纸多屏显示原理文档解读&a…

Vue插槽---slot详解

1、什么是 Vue 插槽&#xff1f;Vue 插槽&#xff08;Slot&#xff09;​​ 是 Vue 提供的一种非常强大且灵活的机制&#xff0c;用于实现&#xff1a;父组件向子组件传递一段模板内容&#xff08;HTML / 组件等&#xff09;&#xff0c;让子组件在指定位置动态渲染这些内容。可…