← 返回题目列表

基本计算器为什么常用栈处理括号和符号?

高频 困难 第 19 / 30 题 更新于 2026/07/29
表达式求值括号

简化版

只含 +-、括号和整数的基本计算器,可以维护当前结果 res、当前符号 sign 和当前数字 num。遇到 ( 时把外层 ressign 入栈并重置;遇到 ) 时先结算当前数字,再用弹出的符号和外层结果合并。

详细版

括号会开启一段新的局部表达式。进入括号前,外层已经累积的结果和括号前的符号必须保存;括号结束后,把括号内结果当成一个整体数字合并回外层。

int calculate(String s) {
    Deque<Integer> stack = new ArrayDeque<>();
    int res = 0, sign = 1, num = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (Character.isDigit(c)) {
            num = num * 10 + (c - '0');
        } else if (c == '+') {
            res += sign * num; num = 0; sign = 1;
        } else if (c == '-') {
            res += sign * num; num = 0; sign = -1;
        } else if (c == '(') {
            stack.push(res); stack.push(sign);
            res = 0; sign = 1;
        } else if (c == ')') {
            res += sign * num; num = 0;
            res = stack.pop() * res + stack.pop();
        }
    }
    return res + sign * num;
}

空格直接跳过。多位数字要累积,最后一个数字也要在循环结束后结算。

完整版教学

一、表达式求值的难点在哪里

对于只有加减法的表达式,运算优先级本身不复杂,真正麻烦的是括号。括号会让一段子表达式先算,并且括号前的符号会作用到整个括号结果。例如 1-(2+3) 不是 1-2+3,而是 1 - 5 = -4

1 - (2 + 3)
外层: 已有 res=1, 括号前 sign=-1
内层: 2+3=5
合并: 1 + (-1)*5 = -4

栈要保存的就是进入括号前的外层现场。

二、三个变量分别代表什么

res 表示当前层已经结算的结果,sign 表示当前数字前面的符号,num 表示正在读取的多位数字。遇到操作符时,把 sign * num 加到 res,然后重置数字并更新符号。

变量含义示例
res当前括号层已结算结果读完 1+2 后是 3
sign当前数字的正负读到 - 后变成 -1
num正在累积的数字123 时依次变 1、12、123

这样不需要等整条表达式结束才处理加减。

三、遇到左括号为什么入栈

左括号表示进入新层。进入前,外层的 res 和括号前的 sign 都必须保存,因为括号内会重新从 0 开始计算。入栈顺序可以是先 ressign,出栈时反过来使用。

表达式: 8-(3-1)
遇到 '(' 前: res=8, sign=-1
入栈: [8, -1]
括号内重新计算: 3-1=2
合并: 8 + (-1)*2 = 6

这个“保存现场 -> 新层计算 -> 恢复现场”就是栈的典型用途。

四、右括号如何合并结果

遇到 ) 时,当前层最后一个数字可能还没结算,所以先执行 res += sign * num。然后弹出括号前的符号 outerSign 和外层结果 outerRes,把当前层结果当成一个整体数字:

res = outerRes + outerSign * innerRes

如果入栈时用了 stack.push(res); stack.push(sign);,那么出栈时先弹到的是 sign,再弹到的是 res。顺序写错会让结果完全偏掉。

五、和逆波兰表达式有什么不同

逆波兰表达式已经把优先级体现在 token 顺序里,求值时遇到操作符弹两个数即可。基本计算器面对的是中缀表达式,必须在扫描时处理括号和符号上下文。

问题栈里保存触发点
逆波兰表达式数字操作数遇到操作符
基本计算器外层结果和符号遇到括号

如果题目加入 */,还要处理乘除优先级,常见做法是用栈保存带符号项,或用表达式解析框架。

六、常见误区与追问

记忆钩子:左括号是“存档开新局”,右括号是“结算本局,再乘上存档里的符号合回去”。

  • 误区:括号前的负号只影响括号里第一个数字。 它影响整个括号结果,例如 -(2+3) 是 -5。
  • 误区:遇到 ) 直接合并,忘记先结算 num 当前层最后一个数字还在 num 中,必须先加入 res
  • 误区:多位数字按单个字符处理。 123 要累积成一个数字,不能当作 1、2、3 分别计算。
  • 追问:空格怎么处理? 空格不是数字、符号或括号,跳过即可。
  • 追问:为什么最后还要 res + sign * num 表达式可能以数字结尾,最后一个数字没有被操作符触发结算。
  • 追问:如果有乘除怎么扩展? 需要额外处理优先级,通常用操作数栈/操作符栈,或把乘除立即合并为一个项。

七、加强记忆

基本计算器的主线是“当前层结算 + 括号层存档”。res/sign/num 负责没有括号时的线性加减;栈负责在 ( 时保存外层结果和符号,在 ) 时把内层结果作为整体合并回去。抓住“括号结果整体受前一个符号影响”,这题就能写清楚。