题目描述

✅ 227. 基本计算器 II

image-20260928195503123

image-20260928195503124

题意分析

给定只包含非负整数、加减乘除和空格的有效表达式,计算最终结果。表达式没有括号,不能使用把整段字符串直接当表达式执行的内置函数。

乘除优先于加减;连续乘除按从左到右的顺序计算,整数除法向零截断。输入的数字可以有多位,空格不参与计算;减法可能让中间项和最终结果为负。题目保证表达式有效且中间结果在 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。

解题步骤

  1. 初始化 result = 0、last = 0、num = 0、op = '+'。
  2. 扫描字符;遇数字,用 num = num * 10 + digit 累积多位数,普通空格不改变数字和运算符。
  3. 遇到运算符或字符串末尾时,用上一个运算符 op 结算 num。
  4. +、-:先把旧 last 加入 result,再令 last = ±num;*、/:直接令 last = last * num 或 last / num。
  5. 结算后将 num 清零,并把当前字符存入 op,供下一个数字使用。末尾也会执行赋值,但不会再使用这个 op。
  6. 扫描结束,返回 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 处理没有括号的数值表达式。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/14490119
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!