LeetCode 227. 基本计算器 II
题目描述


题意分析
给定只包含非负整数、加减乘除和空格的有效表达式,计算最终结果。表达式没有括号,不能使用把整段字符串直接当表达式执行的内置函数。
乘除优先于加减;连续乘除按从左到右的顺序计算,整数除法向零截断。输入的数字可以有多位,空格不参与计算;减法可能让中间项和最终结果为负。题目保证表达式有效且中间结果在 32 位有符号整数范围内。
解法:一次扫描维护当前项
核心思路
[!blue]
可以把整个表达式看成若干带符号项的和,每一项内部只有乘除。乘除需要先算完,所以不能一读到数字就把它加入总和;只需把最后一项单独留下,等待判断后面是否还有乘除。
用
result保存已经结束的项之和,last保存最近一个尚可能继续乘除的带符号项。num累积当前正在读取的数字,op保存这个数字前面的运算符。遇到新运算符时,说明当前数字已经读完,使用旧的op处理它,而不是使用刚读到的新运算符。若旧
op是加号或减号,说明num开启了新的一项,旧last已经不会再参与后续乘除,可以加入result,再令last = num或last = -num。若旧op是乘号或除号,当前数字仍属于同一项,只更新last,暂不合入总和。这样既保留了乘除优先级,也按扫描顺序完成了同级运算。减号直接并入
last的符号即可。因为后续读入的数非负,而且除法向零截断,带负号的项逐次乘除,与先计算该项的大小再从总和减去一致。Java 和 Go 的整数除法都符合这一截断规则。开始时设
op = '+',把第一个数字统一视作正项。数字按十进制累积,普通空格不触发结算;但到达字符串最后一个字符时,无论它是数字还是空格,都必须结算最后一个num。循环后最后一项仍在last中,返回result + last。
解题步骤
- 初始化
result = 0、last = 0、num = 0、op = '+'。- 扫描字符;遇数字,用
num = num * 10 + digit累积多位数,普通空格不改变数字和运算符。- 遇到运算符或字符串末尾时,用上一个运算符
op结算num。+、-:先把旧last加入result,再令last = ±num;*、/:直接令last = last * num或last / num。- 结算后将
num清零,并把当前字符存入op,供下一个数字使用。末尾也会执行赋值,但不会再使用这个op。- 扫描结束,返回
result + last,补上尚未合并的最后一项。
代码实现
class Solution {
public int calculate(String s) {
int result = 0;
int last = 0;
int num = 0;
char op = '+';
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
boolean isDigit = ch >= '0' && ch <= '9';
if (isDigit) {
num = num * 10 + ch - '0';
}
// 遇到新运算符或末尾,用之前保存的运算符结算完整数字。
if ((!isDigit && ch != ' ') || i == s.length() - 1) {
if (op == '+') {
result += last;
last = num;
} else if (op == '-') {
result += last;
last = -num;
} else if (op == '*') {
last *= num;
} else {
last /= num;
}
op = ch;
num = 0;
}
}
// 最后一项仍在 last 中,返回时补入总和。
return result + last;
}
}
func calculate(s string) int {
result, last := 0, 0
num := 0
op := byte('+')
for i := 0; i < len(s); i++ {
ch := s[i]
isDigit := ch >= '0' && ch <= '9'
if isDigit {
num = num*10 + int(ch-'0')
}
// 遇到新运算符或末尾,用之前保存的运算符结算完整数字。
if (!isDigit && ch != ' ') || i == len(s)-1 {
if op == '+' {
result += last
last = num
} else if op == '-' {
result += last
last = -num
} else if op == '*' {
last *= num
} else {
last /= num
}
op = ch
num = 0
}
}
// 最后一项仍在 last 中,返回时补入总和。
return result + last
}
复杂度分析
- 时间复杂度:$O(n)$,其中
n为表达式长度,每个字符只扫描一次。- 空间复杂度:$O(1)$,只维护四个状态变量。
关键点总结
[!green]
- 结算当前数字时使用的是它左边的“上一个运算符”,不是刚读到的新运算符。
result保存已确定项,last单独保留可能继续参与乘除的末项,这就是优先级处理的核心。- 到达字符串末尾也必须触发一次结算;Java 和 Go 的整数除法都向零截断。
- 连续乘除直接更新同一个
last,按从左到右的顺序结算,无需额外保存所有项。
易错点总结
[!yellow]
- 只在遇到运算符时结算,会漏掉最后一个数字;应额外判断当前位置是否为字符串末尾。
- 遇到空格就直接跳过当前循环,可能漏掉末尾空格触发的最终结算。普通空格不更新
op,末尾空格仍要处理最后一项。- 使用刚读到的运算符处理
num,会把数字与右侧运算符错误关联;应先按旧op结算,再记录新运算符。- 每位数字都需要累积进
num,并且每次结算后清零,否则会拆散多位数或混入下一个数字。- 把负数除法当作向下取整,会得到错误结果;本题要求丢弃小数部分、向零截断。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 224. 基本计算器 | 困难 | 原题支持括号与加减,本题增加乘除优先级,遇乘除时需先合并。 |
| 772. 基本计算器 III | 困难 | 在本题四则运算基础上加入括号嵌套,可用递归或额外状态栈处理。 |
| 394. 字符串解码 | 中等 | 用栈保存嵌套表达式的中间状态;本题优先结算乘除再合并加减,该题展开带重复次数的嵌套片段。 |
| 770. 基本计算器 IV | 困难 | 计算器系列。IV 加入括号和变量,中间结果改为多项式;II 处理没有括号的数值表达式。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!