227. 基本计算器 II

news2024/11/20 22:38:37

227. 基本计算器 II

    • 题目
    • 算法设计:栈
    • 扩展:后缀表达式

 


题目

传送门:https://leetcode.cn/problems/basic-calculator-ii/submissions/

 


算法设计:栈

一个功能完备的计算器功能,有很多功能,我们需要从最简单的功能迭代起来。

先宏观,再微分:

  • 输入:字符串,需要把字符串转为数字
  • 功能:加减、乘除优先、括号优优先
  • 细节:读到空格跳过、不溢出整型最大值

计算的关键在于优先级处理,括号 > 乘除 > 加减。
 


第一步,字符串转整数。

int str_to_int( string s ){            // 字符串转整数
	int num = 0;
	for( int i=0; str.length(); i++ )
		if( isdigit(str[i]) )          // 输入字符串为数字
			num = 10 * num + (c - '0');
}

第二步,实现加减。

  • 核心思路是把字符串分解成符号和数字的组合,如 2 - 3,变成 +2、-3。
int calculate(string s) {
    stack<int> stk;                                 // 记录算式中的数字
    int num = 0;                                    // 保存字符串转数字后的数字
    char sign = '+';                                // 记录每个数字之前的运算符,因为第一个数字没有符号的(3-2),设置为符号+(+3-2),如果第一个数是负数,那就是 +0
    for (int i = 0; i < s.size(); i++) {            // 遍历字符串
        if (isdigit(s[i]))                          // 读到数字
            num = 10 * num + (s[i] - '0');          // "123" -> 100 + 20 + 3 -> 123
         if ( (!isdigit(c) && c != ' ') || i == s.size() - 1 ) {  // s[i]是符号且不是空格(运算符) or 最后一个字符之后没有符号可读取了,所以也要入栈直接计算
            switch (sign) {                         // 看数字前的 sign 来决定怎么处理 s[i] 前面的数
                case '+':
                    stk.push(num); break;           // 加法将之前的数字入栈
                case '-':
                    stk.push(-num); break;          // 加法将之前数字的相反数入栈
            }
            sign = s[i];                            // 更新符号为当前符号
            // 把 3-2 代入分析,初始sign为+,遍历到s[0]把'3'->3,再遍历s[1]到'-',后遍历到s[2]让+3入栈,更新sign为'-'。
            num = 0;                                // 数字清零,计算下一个数字
        }
    }
    // 将栈中所有结果求和就是答案
    int res = 0;
    while (!stk.empty()) {
        res += stk.top();
        stk.pop();
    }
    return res;
}

第三步,实现乘除。

  • 核心思路依然是把字符串分解成符号和数字的组合,如 1*4,如 +1、*4。

乘除法优先于加减法体现在,乘除法可以和栈顶的数结合,而加减法只能把自己放入栈。

class Solution {
public:
    int calculate(string s) {
    	stack<int> stk;                                 
    	int num = 0;                                    
    	char sign = '+';                                
    	for (int i = 0; i < s.size(); i++) {
    		if (isdigit(s[i])) 
        		num = 10 * num + (s[i] - '0');
    		if ( (!isdigit(s[i]) && s[i] != ' ') || i == s.size() - 1 ) {
        		switch (sign) {
            		int pre;
            		case '+':
                		stk.push(num); break;
            		case '-':
                		stk.push(-num); break;
            		case '*':
                		pre = stk.top();             // 只要拿出前一个数字做对应运算即可
                		stk.pop();
                		stk.push(pre * num);
                		break;
            		case '/':
                		pre = stk.top();             // 只要拿出前一个数字做对应运算即可
                		stk.pop();
                		stk.push(pre / num);
                		break;
        		}
        		sign = s[i];
        		num = 0;
    		}
		}
    	// 将栈中所有结果求和就是答案
    	int res = 0;
    	while (!stk.empty()) {
        	res += stk.top();
        	stk.pop();
    	}
    	return res;
	}
};

 


第四步,处理括号。

括号具有递归性质。

calc(3 * (4 - 5/2) - 6)
= 3 * calc(4 - 5/2) - 6
= 3 * 2 - 6
= 0

无论多少层括号嵌套,通过 calc 函数递归调用自己,都可以将括号中的算式化简成一个数字。

括号包含的算式,直接视为一个数字就可。

  • 遇到 ( 开始递归
  • 遇到 ) 结束递归
class Solution:
    def calculate(self, s: str) -> int:
        def calc(s: List) -> int:
            stack = []
            sign = '+'
            num = 0
            while len(s) > 0:
                c = s.popleft()
                if c.isdigit():
                    num = 10 * num + int(c)
                    
                # 遇到左括号开始递归计算 num
                if c == '(':
                    num = calc(s)

                if (not c.isdigit() and c != ' ') or len(s) == 0:
                    if sign == '+':
                        stack.append(num)
                    elif sign == '-':
                        stack.append(-num)
                    elif sign == '*':
                        stack[-1] = stack[-1] * num
                    elif sign == '/':
                        # python 除法向 0 取整的写法
                        stack[-1] = int(stack[-1] / float(num))       
                    num = 0
                    sign = c
                    
                # 遇到右括号返回递归结果
                if c == ')': break
                
            return sum(stack)
        return calc(collections.deque(s))

你看,加了两三行代码,就可以处理括号了。

C++ 完整代码:

class Solution {
public:
    int calculate(string str) {
        int index = 0;
        return calc(str, index);
    }

    int calc(string &s, int &index) {
        stack<int> tokens;
        int num = 0;
        char sign = '+';

        for (; index < s.size() + 1; ++index) {
            char c = s[index];
            if (isdigit(c)) 
                num = num * 10 + (c - '0');
            if (c == '(') {
                index++;
                num = calc(s, index);
                continue;
            }
            if (!isdigit(c) && c != ' ') {
                int temp;
                switch (sign) {
                    case '+':
                        tokens.push(num);
                        break;
                    case '-':
                        tokens.push(-num);
                        break;
                    case '*':
                        temp = tokens.top();
                        tokens.pop();
                        tokens.push(temp * num);
                        break;
                    default:
                        temp = tokens.top();
                        tokens.pop();
                        tokens.push(temp / num);
                }
                num = 0;
                sign = c;
            }
            if (c == ')')
                break;
        }
        int result = 0;
        while (!tokens.empty()) {
            result += tokens.top();
            tokens.pop();
        }
        return result;
    }
};

至此,计算器的全部功能就实现了,通过对问题的层层拆解化整为零。
 


扩展:后缀表达式

计算思路:先把中缀表达式转化为后缀表达式,所有符号都在运算数字后面出现,可以不用括号了。

  • 中缀表达式:9 + (3-1) * 3 + 10 / 2
  • 后缀表达式:9 3 1 - 3 * + 10 2 / +

再从左到右遍历后缀表达式,遇到数字就进栈,遇到符号就将处于栈顶俩个数字出栈运算,运算结果入栈,一直重复,就可以计算出最终结果。

那怎么把中缀表达式转化为后缀表达式?

从左到右遍历中缀表达式的每个数字和符号,若是数字就加入后缀表达式。

引入一个栈存储符号:

  • 若是左括号,入栈
  • 若是右括号则一直出栈直到左括号出栈为止,出栈后加入后缀表达式
  • 若此符号优先级高于栈顶符号(乘除 > 加减),入栈
  • 若此符号优先级低于栈顶符号(加减 < 乘除),则栈顶元素全部出栈并加入后缀表达式,并将当前符号进栈
  • 一直到最终的后缀表达式

举例,遍历中缀表达式 9 + (3-1) * 3 + 10 / 2。

第 1 个字符是数字 9,加入后缀表达式

  • 后缀表达式:9

第二个字符是符号 +,入栈

第三个字符是符号 ‘(’,因为是左括号还没配对,入栈。

第四个字符是数字 3,加入后缀表达式

  • 后缀表达式:9 3


第五个字符 ‘-’,入栈。

第六个数字 1,加入后缀表达式。

第七个字符 ‘)’,若是右括号则一直出栈直到左括号出栈为止,出栈后加入后缀表达式。

  • 后缀表达式:9 3 1 -

第八个数字 3,加入后缀表达式。

  • 后缀表达式:9 3 1 - 3

第九个符号 ‘*’,栈顶元素是 ‘+’,此符号大于栈顶元素,入栈。


第十个字符 ‘+’,栈顶元素是 ‘*’,此符号小于栈顶元素,栈中元素全部出栈(没有比 + 号更低的优先级,所以全部出栈),并加入后缀表达式,并将当前符号进栈。

  • 后缀表达式:9 3 1 - 3 * +

第十一个数字 10,加入后缀表达式

  • 后缀表达式:9 3 1 - 3 * + 10

第十二个字符 ‘/’,栈顶元素是 ‘+’,若此符号优先级高于栈顶符号(乘除 > 加减),入栈

第十三个数字 2,加入后缀表达式

  • 后缀表达式:9 3 1 - 3 * + 10 2

因为是字符串的最后一个字符,所以栈中全部字符全部出栈并加入后缀表达式

  • 后缀表达式:9 3 1 - 3 * + 10 2 / +

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

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

相关文章

【CAD .NET】第一课 开发自己的CAD软件

开发自己的CAD软件(解析库私信我buy) 介绍 CAD .NET 为在 .NET 环境下开发解决方案的库。它支持 AutoCAD DWG、DXF、PLT 和其他 CAD 格式。它支持 AutoCAD DWG, DXF, PLT 和其他 CAD 格式。 该库可用于广泛领域: 处理所有项目阶段的工业图纸 监测和远程控制程序 数控(C…

ReactNative0.71正式版发布,Ts作为首要开发语言

时隔近4个月的时间&#xff0c;ReactNative带来了0.71版本的更新&#xff0c;这速度对比隔壁Flutter不得不说是真的有的慢。 这个版本的更新的内容还是比较重磅和突破性的&#xff0c;主要体现在如下几点&#xff1a; 编程语法默认改为TypeScript使用Flexbox Gap使布局更加简…

AlmaLinux 9部署JumpServer

JumpServer简介 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。 JumpServer 使用 Python 开发&#xff0c;配备了业界领先的 Web Terminal 方案&#xff0c;交互界面美观、用户体验好。 JumpServer 采纳分布式架构&#xff0c;支持多…

【C++11】—— 类的新功能

目录 一、移动构造和移动赋值的特点 二、类成员变量初始化 三、强制生成默认函数的关键字default 四、禁止生成默认函数的关键字delete 五、继承和多态中的fifinal与override关键字 一、移动构造和移动赋值的特点 默认成员函数 原来C类中&#xff0c;有6个默认成员函数&a…

Yolov8实例分割Tensorrt部署实战

目录 0 引言 1 生成onnx模型 2 onnx转为tensorrt的engine模型 3 Tensorrt推理 3.1 yolov8n-seg分割结果 3.2 yolov8s-seg分割结果 3.3 yolov8m-seg分割结果 3.4 yolov8l-seg分割结果 3.5 yolov8x-seg分割结果 0 引言 ultralytics在github发布了yolov8模型&#xff0c;…

C语言 atoi 函数解析

文章目录前言atoi函数的介绍atoi函数的使用atoi函数的自我实现写在最后前言 对于atoi函数大家可能会有些陌生&#xff0c;不过当你选择并阅读到这里时&#xff0c;请往下阅读&#xff0c;我相信你能对atoi函数熟悉该函数的头文件为 <stdlib.h> 或 <cstdlib> atoi函…

Android Studio Electric Eel | 2022.1.1 发布,快来看看有什么大更新吧

原文链接&#xff1a;https://developer.android.com/studio/releases 本次 Android Studio 又发布了大量的内容更新&#xff0c;按照惯例推荐是等两个小版本后再更新会比较稳&#xff0c;当然也鼓励大家尝尝鲜&#xff0c;本次更新推出了大量实用的功能。 另外由于国内对更新…

大学生在校和校外可以开展的兼职,你肯定不知道

大学生的空闲时间比较多&#xff0c;适合大学生的兼职也有很多&#xff0c;因为大学生逻辑思维更强、大脑灵活、接受新事物能力强&#xff0c;而且大多都有电脑手机&#xff0c;可以做一点网上的兼职。如果你在学校有一定条件的话&#xff0c;还可以开展在学校里另类的兼职&…

1580_AURIX_TC275_SMU模块初步

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) SMU集中了所有软硬件的Alarm信息&#xff0c;这个在之前的很多模块的描述中看得出来的。默认情况下&#xff0c;其实只有看门狗的Alarm是开的&#xff0c;其他的都是关的。这个描述跟我之前…

[ 问题解决篇 ] 设置windows密码策略并且更改用户密码 -- 解决windwos密码无法设置为1的问题

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

P3654 First Step (ファーストステップ)

P3654 First Step (ファーストステップ) 题目背景 知らないことばかりなにもかもが&#xff08;どうしたらいいの&#xff1f;&#xff09; 一切的一切 尽是充满了未知数&#xff08;该如何是好&#xff09; それでも期待で足が軽いよ&#xff08;ジャンプだ&#xff01;&…

C语言 全排列(包含错误代码及分析,memset简单介绍及举例)

正确代码&#xff1a;#include <stdio.h> #include <math.h> #include <string.h>int n;//表示位数 int a[10]; int hash_tabel[10];void print() {for(int in;i>0;i--)printf("%d",a[i]);printf("\n"); } void core(int d) {if(d0)/…

Linux使用ACL控制对文件的访问

文章目录1. 查看文件ACL2. 解释文件ACL3. 更改ACL文件权限setfacl命令1) 以递归方式更新现有 cases 目录及其内容。2) 以递归方式更新现有cases 目录及其内容。3) 为contractors 组成员更新默认权限。默认权限为读取、写入和执行3.1.4) 为contractor3用户更新默认权限。默认权限…

ssm权限管理系统2

PageHelper 直接使用maven在pom.xml中添加依赖就行 在我们这个ssm管理项目中&#xff0c;已经添加了依赖包 在Springp配置文件中配置拦截器插件 正式使用 我们只需要在调用dao的方法之前插入如下语句就行&#xff0c;也就是说在service层里面进行插入 当然了上面的pageNum与pag…

ssh公钥配置,使用git从github上拉取、上传项目

一、ssh公钥配置若在以下某个步骤出现问题&#xff1a;Please make sure you have the correct access rights and the repository exists 则按本节方法重新配置ssh。删除C:\Users\Administrator\.ssh下的所有文件在桌面右击&#xff0c;选择Git Bash Here# 1.设置用户名 git c…

万字讲解Linux常用指令

目录 前言&#xff1a; 一、Linux界面问题 二、什么是操作系统 三、为什么学习Linux基本指令 四、Linux基础指令 pwd命令 ls指令 认识一下ls -a&#xff1a; 认识一下ls -d&#xff1a; 理解文件 cd指令 4.touch指令 5.mkdir指令 6.rmdir指令和rm指令 7.man指令 8.cp指令 9.mv指…

自动驾驶中3D目标检测综述

1 背 景 1.1 3D目标检测 3D目标检测是通过输入传感器数据&#xff0c;预测3D目标的属性信息的任务。如何表示3D目标的属性信息是关键&#xff0c;因为后续的预测和规划需要这些信息。在大部分情况下&#xff0c;3D目标被定义为一个立方体&#xff0c;(x,y,z)是立方体的中心坐…

stm32mp1 uboot启动流程分析

stm32mp1 uboot启动流程分析 本节主要关注uboot启动linux的流程&#xff0c;首先关注下uboot的环境变量 uboot环境变量 进入uboot以后回车输入print即可看到uboot的所有环境变量&#xff1a; 这里很多变量嵌套了一些流程&#xff0c;整理一下格式&#xff1a; altbootcmdru…

实现系统调用

文章目录前言前置知识实验操作实现一实验二实验三实验四实验五前言 博客记录《操作系统真象还原》第十二章实验的操作~ 实验环境&#xff1a;ubuntu18.04VMware &#xff0c; Bochs下载安装 实验内容&#xff1a; 实现系统调用。实现write系统调用。实现printf。 3.1 仅支持…

Simulink 自动代码生成电机控制:关于无传感控制开环启动控制的仿真和开发板运行

目录 开环启动原理 开环启动建模实现 开环启动仿真 代码生成和验证 总结 开环启动原理 永磁同步电机开环三步启动是比较传统也是比较常用的启动方式&#xff0c;典型的启动有&#xff1a; 对齐&#xff1a;也说是说的转子预定位&#xff0c;就是通过手动给定一个初始角度…