目录

题目描述

227. 基本计算器 II

题意分析

输入是一个只含非负整数、四种运算符 +-*/ 和空格的合法表达式字符串,要求返回它的整数计算值。

约束里的关键信号有三个。其一,表达式没有括号,运算符只有「乘除高、加减低」两级优先级,这是它与 224 题最本质的区别。其二,字符串里可能夹杂空格(如 " 3/2 "),空格只该被跳过,不携带任何语义。其三,除法是整数除法且向零截断-14 / 3 应得 -4 而不是向下取整的 -5——虽然输入的数都非负,但中间结果可能为负,截断方向会真实影响答案。

边界上还要注意:数字可能是多位数,不能按单字符读取;题目保证表达式合法且所有中间结果都在 32 位整数范围内,因此无需处理非法输入与溢出。

解法:一次扫描维护当前项

核心思路

问题关键:表达式只有两级优先级。加减可以把每一项最终累加,乘除却必须先作用在最近的一项上。栈能保存所有项,但只有最后一项会被后续乘除修改,因此两个变量就够了。

维护 result 表示已经确定、不会再被乘除影响的项之和,last 表示最近一个带符号的项,num 表示正在读取的数字,op 表示 num 左边的运算符。读完一个数字后:加减把旧 last 结算进 result,再开启新项;乘除直接更新 last

不变量:每次结算后,已扫描表达式的值等于 result + last,且只有 last 可能被下一个乘除继续改变。扫描结束返回两者之和,因此既保持运算优先级,也不需要额外栈空间。

解题步骤

  1. 初始化 result = 0last = 0num = 0op = '+'
  2. 扫描字符;遇数字,用 num = num * 10 + digit 读取多位数,空格跳过。
  3. 遇到运算符或字符串末尾时,用上一个运算符 op 结算 num
  4. +-:先把旧 last 加入 result,再令 last = ±num*/:直接令 last = last * numlast / num
  5. 重置 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. 计算器 中等 与本题同题面,白板高频复写变体