当前位置:网站首页>【表达式计算】表达式计算问题的通用解法(练习加强版,含总结)
【表达式计算】表达式计算问题的通用解法(练习加强版,含总结)
2022-07-29 13:59:00 【宫水三叶的刷题日记】
题目描述
这是 LeetCode 上的 227. 基本计算器 II ,难度为 中等。
Tag : 「表达式计算」
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
整数除法仅保留整数部分。
示例 1:
输入:s = "3+2*2"
输出:7
示例 2:
输入:s = " 3/2 "
输出:1
示例 3:
输入:s = " 3+5 / 2 "
输出:5
提示:
s
由整数和算符 ('+'
,'-'
,'*'
,'/'
) 组成,中间由一些空格隔开s
表示一个 有效表达式表达式中的所有整数都是非负整数,且在范围 内 题目数据保证答案是一个 32-bit
整数
双栈
如果你有看这篇 224. 基本计算器 的话,今天这道题就是道练习题。
帮你巩固 双栈解决「通用表达式」问题的通用解法 。
事实上,我提供这套解决方案不仅仅能解决只有 + - ( )
的 [224. 基本计算器] 或者 + - * /
[227. 基本计算器 II(本题)] 的表达式问题,还能解决 + - * / ^ % ( )
的完全表达式问题。
甚至支持自定义运算符,只要在运算优先级上进行维护即可。
对于「表达式计算」这一类问题,你都可以使用这套思路进行解决。我十分建议你加强理解这套处理逻辑。
对于「任何表达式」而言,我们都使用两个栈 nums
和 ops
:
nums
: 存放所有的数字ops
:存放所有的数字以外的操作
然后从前往后做,对遍历到的字符做分情况讨论:
空格 : 跳过 (
: 直接加入ops
中,等待与之匹配的)
)
: 使用现有的nums
和ops
进行计算,直到遇到左边最近的一个左括号为止,计算结果放到nums
数字 : 从当前位置开始继续往后取,将整一个连续数字整体取出,加入 nums
+ - * / ^ %
: 需要将操作放入ops
中。 在放入之前先把栈内可以算的都算掉(只有「栈内运算符」比「当前运算符」优先级高/同等,才进行运算),使用现有的nums
和ops
进行计算,直到没有操作或者遇到左括号,计算结果放到nums
我们可以通过 来理解 只有「栈内运算符」比「当前运算符」优先级高/同等,才进行运算 是什么意思:
因为我们是从前往后做的,假设我们当前已经扫描到 2 + 1
了(此时栈内的操作为 +
)。
如果后面出现的 + 2
或者- 1
的话,满足「栈内运算符」比「当前运算符」优先级高/同等,可以将2 + 1
算掉,把结果放到nums
中;如果后面出现的是 * 2
或者/ 1
的话,不满足「栈内运算符」比「当前运算符」优先级高/同等,这时候不能计算2 + 1
。
一些细节:
由于第一个数可能是负数,为了减少边界判断。一个小技巧是先往 nums
添加一个 0为防止 () 内出现的首个字符为运算符,将所有的空格去掉,并将 (-
替换为(0-
,(+
替换为(0+
(当然也可以不进行这样的预处理,将这个处理逻辑放到循环里去做)从理论上分析, nums
最好存放的是long
,而不是int
。因为可能存在大数 + 大数 + 大数 + … - 大数 - 大数
的表达式导致中间结果溢出,最终答案不溢出的情况
代码:
class Solution {
// 使用 map 维护一个运算符优先级
// 这里的优先级划分按照「数学」进行划分即可
Map<Character, Integer> map = new HashMap<>(){
{
put('-', 1);
put('+', 1);
put('*', 2);
put('/', 2);
put('%', 2);
put('^', 3);
}};
public int calculate(String s) {
// 将所有的空格去掉
s = s.replaceAll(" ", "");
char[] cs = s.toCharArray();
int n = s.length();
// 存放所有的数字
Deque<Integer> nums = new ArrayDeque<>();
// 为了防止第一个数为负数,先往 nums 加个 0
nums.addLast(0);
// 存放所有「非数字以外」的操作
Deque<Character> ops = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
char c = cs[i];
if (c == '(') {
ops.addLast(c);
} else if (c == ')') {
// 计算到最近一个左括号为止
while (!ops.isEmpty()) {
if (ops.peekLast() != '(') {
calc(nums, ops);
} else {
ops.pollLast();
break;
}
}
} else {
if (isNumber(c)) {
int u = 0;
int j = i;
// 将从 i 位置开始后面的连续数字整体取出,加入 nums
while (j < n && isNumber(cs[j])) u = u * 10 + (cs[j++] - '0');
nums.addLast(u);
i = j - 1;
} else {
if (i > 0 && (cs[i - 1] == '(' || cs[i - 1] == '+' || cs[i - 1] == '-')) {
nums.addLast(0);
}
// 有一个新操作要入栈时,先把栈内可以算的都算了
// 只有满足「栈内运算符」比「当前运算符」优先级高/同等,才进行运算
while (!ops.isEmpty() && ops.peekLast() != '(') {
char prev = ops.peekLast();
if (map.get(prev) >= map.get(c)) {
calc(nums, ops);
} else {
break;
}
}
ops.addLast(c);
}
}
}
// 将剩余的计算完
while (!ops.isEmpty()) calc(nums, ops);
return nums.peekLast();
}
void calc(Deque<Integer> nums, Deque<Character> ops) {
if (nums.isEmpty() || nums.size() < 2) return;
if (ops.isEmpty()) return;
int b = nums.pollLast(), a = nums.pollLast();
char op = ops.pollLast();
int ans = 0;
if (op == '+') ans = a + b;
else if (op == '-') ans = a - b;
else if (op == '*') ans = a * b;
else if (op == '/') ans = a / b;
else if (op == '^') ans = (int)Math.pow(a, b);
else if (op == '%') ans = a % b;
nums.addLast(ans);
}
boolean isNumber(char c) {
return Character.isDigit(c);
}
}
时间复杂度: 空间复杂度:
总结
还记得我在 题解 留的「进阶」内容?
如果 + -
基础上,再考虑*
和/
,需要增加什么考虑?如何维护运算符的优先级?
这个进阶问题就对应了 LeetCode 上的两道题:
227. 基本计算器 II :本题,包含符号 + - * /
772. 基本计算器 III :有锁题,包含符号 + - * / ( )
在「问题1」的基础上,如果考虑支持自定义符号,例如 a / func(a, b) * (c + d),需要做出什么调整?
这个进阶问题,在 LeetCode 上也有类似的题目:
770. 基本计算器 IV : 包含自定义函数符号
综上,使用三叶提供的这套「双栈通用解决方案」,可以解决所有的「表达式计算」问题。因为这套「表达式计算」处理逻辑,本质上模拟了人脑的处理逻辑:根据下一位的运算符优先级决定当前运算符是否可以马上计算。
最后
这是我们「刷穿 LeetCode」系列文章的第 No.227
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地
本文由 mdnice 多平台发布
边栏推荐
猜你喜欢
【微信小程序】全局配置
通过二维顺序表实现杨辉三角
新来技术总监:谁在用 isXxx 形式定义布尔类型,明天不用来了!
The Advanced Guide to the Computer Professional Interview
国产手机将用户变成它们的广告肉鸡,难怪消费者都买iPhone了
2022年了!还在用定时器实现动画?赶紧试试requestAnimationFrame吧!
计算机专业面试进阶指南
企业需要知道的5个 IAM 最佳实践
iMedicalLIS监听程序(1)
Children's programming electronics (graphical programming Scratch secondary level exam parsing (choice) in June 2022
随机推荐
还在开发短信验证码登录?试试(本机号码一键登录)
app小程序开发的营销优势有什么?
有关包装类的一道经典面试题
The key to cracking AI full-process development problems
期货合约知多少
阿里巴巴 CTO 程立:开源是基础软件的源头!
第4章_3——索引的使用
Still developing SMS verification code login?Try it (one-click login with your phone number)
The new technical director, who is in the form of a isXxx Boolean type definition, tomorrow need not come!
leetcode linked list topic
2022年了!还在用定时器实现动画?赶紧试试requestAnimationFrame吧!
AQS源码阅读与强软弱虚4种引用以及ThreadLocal原理与源码
[10:00 Open Class]: Application Exploration of Kuaishou GPU/FPGA/ASIC Heterogeneous Platform
国产手机将用户变成它们的广告肉鸡,难怪消费者都买iPhone了
2022杭电多校第三场
暴力递归到动态规划 02 (绝顶聪明的人的纸牌游戏)
系列文章|云原生时代下微服务架构进阶之路 - Boris
AI全流程开发难题破解之钥
web会话管理与xss攻击
1184. 欧拉回路