LeetCode 227. 基本计算器 II
题目描述
题意分析
输入是一个只含非负整数、四种运算符
+、-、*、/和空格的合法表达式字符串,要求返回它的整数计算值。约束里的关键信号有三个。其一,表达式没有括号,运算符只有「乘除高、加减低」两级优先级,这是它与 224 题最本质的区别。其二,字符串里可能夹杂空格(如
" 3/2 "),空格只该被跳过,不携带任何语义。其三,除法是整数除法且向零截断:-14 / 3应得-4而不是向下取整的-5——虽然输入的数都非负,但中间结果可能为负,截断方向会真实影响答案。边界上还要注意:数字可能是多位数,不能按单字符读取;题目保证表达式合法且所有中间结果都在 32 位整数范围内,因此无需处理非法输入与溢出。
解法:一次扫描维护当前项
核心思路
问题关键:表达式只有两级优先级。加减可以把每一项最终累加,乘除却必须先作用在最近的一项上。栈能保存所有项,但只有最后一项会被后续乘除修改,因此两个变量就够了。
维护
result表示已经确定、不会再被乘除影响的项之和,last表示最近一个带符号的项,num表示正在读取的数字,op表示num左边的运算符。读完一个数字后:加减把旧last结算进result,再开启新项;乘除直接更新last。不变量:每次结算后,已扫描表达式的值等于
result + last,且只有last可能被下一个乘除继续改变。扫描结束返回两者之和,因此既保持运算优先级,也不需要额外栈空间。
解题步骤
- 初始化
result = 0、last = 0、num = 0、op = '+'。- 扫描字符;遇数字,用
num = num * 10 + digit读取多位数,空格跳过。- 遇到运算符或字符串末尾时,用上一个运算符
op结算num。+、-:先把旧last加入result,再令last = ±num;*、/:直接令last = last * num或last / num。- 重置
num并记录当前运算符,最后返回result + last。例如
3+2*2:处理前两个数后result = 3, last = 2;最后的乘法只把last改成4,答案为3 + 4 = 7。
代码实现
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;
}
}
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
}
}
return result + last
}
复杂度分析
- 时间复杂度:$O(n)$,每个字符只扫描一次。
- 空间复杂度:$O(1)$,只维护四个状态变量。
关键点总结
- 结算当前数字时使用的是它左边的“上一个运算符”,不是刚读到的新运算符。
result保存已确定项,last单独保留可能继续参与乘除的末项,这就是优先级处理的核心。- 到达字符串末尾也必须触发一次结算;Java 和 Go 的整数除法都向零截断。
- 若加入括号,需要用栈或递归保存外层状态,本题的常量空间写法不再直接适用。
易错点总结
- 只在遇到运算符时结算:
"3+2*2"会漏掉最后一个2。- 把空格当运算符:
" 3/2 "会提前结算并覆盖op,空格必须跳过。- 用当前运算符结算:扫描到
*时,刚读完的数字应由它前面的运算符处理。- 忘记多位数累积或结算后清零:
"12+3"会被错误读取。- 在不向零截断的语言中直接用向下取整:
"1-14/3"应为-3,不能把-14/3算成-5。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 150. 逆波兰表达式求值 | 中等 | 后缀表达式无优先级问题,纯栈求值 |
| 224. 基本计算器 | 困难 | 含括号的加减,符号与结果状态压栈 |
| 772. 基本计算器 III | 困难 | 括号与乘除并存,本题的完全体 |
| 770. 基本计算器 IV | 困难 | 带变量的表达式化简与多项式合并 |
| LCR 036. 逆波兰表达式求值 | 中等 | 150 的镜像题,操作数出栈顺序 |
| 面试题 16.26. 计算器 | 中等 | 与本题同题面,白板高频复写变体 |