问题描述
给你一个字符串表达式 s
,请你实现一个基本计算器来计算并返回它的值。
注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval()
。
示例
示例 1:
1 | 输入:s = "1 + 1" |
示例 2:
1 | 输入:s = " 2-1 + 2 " |
示例 3:
1 | 输入:s = "(1+(4+5+2)-3)+(6+8)" |
约束条件
- $1 \leq s.length \leq 3 \times 10^5$
s
由数字、'+'
、'-'
、'('
、')'
、和' '
组成s
表示一个有效的表达式'+'
不能用作一元运算的正号'-'
可以用作一元运算的负号- 输入中不存在两个连续的操作符
解题思路
这道题的核心在于处理带括号的表达式计算。关键难点是:
- 符号的传递:括号内的符号需要与括号外的符号相乘
- 优先级处理:括号具有最高优先级
- 状态保存:进入括号时需要保存当前状态
核心思想
使用栈来处理括号的嵌套:
- 遇到
(
:将当前的结果和符号压入栈中,重新开始计算 - 遇到
)
:从栈中弹出之前的结果和符号,与当前结果合并
算法步骤
-
初始化变量:
stack
:存储历史状态的栈sign
:当前数字的符号(1 或 -1)res
:当前表达式的计算结果
-
遍历字符串:
- 数字:解析完整数字,加到结果中
- 符号
+/-
:更新下一个数字的符号 - 左括号
(
:保存当前状态到栈,重置计算 - 右括号
)
:恢复之前状态,合并计算结果
-
状态转换图:
1 | 普通计算 ──┐ |
实现细节
关键点分析
-
符号处理:
- 使用
sign
变量记录当前数字的符号 - 遇到
+
设置sign = 1
,遇到-
设置sign = -1
- 使用
-
括号处理:
- 左括号:
stack.push(sign)
→stack.push(res)
→ 重置状态 - 右括号:弹出
preRes
和preSign
,计算preSign * res + preRes
- 左括号:
-
数字解析:
- 连续读取数字字符,构建完整的数字
- 注意处理多位数字的情况
执行过程示例
以 "(1+(4+5+2)-3)+(6+8)"
为例:
1 | 步骤详解: |
代码实现
1 | func calculate(s string) int { |
复杂度分析
时间复杂度
- 时间复杂度:$O(n)$
- 每个字符最多被访问一次,数字字符可能需要额外的内层循环来构建完整数字
- 栈操作(入栈、出栈)都是 $O(1)$ 时间复杂度
空间复杂度
- 空间复杂度:$O(n)$
- 在最坏情况下(如
"((((1))))"
),栈的深度等于括号的嵌套深度 - 最大嵌套深度可能达到 $O(n)$
关键收获
核心思想
- 栈处理括号:使用栈保存和恢复计算状态是处理嵌套结构的经典方法
- 符号传递:括号内外符号的正确传递是解题关键
- 状态管理:合理设计状态变量(
res
、sign
、stack
)
常见陷阱
- 多位数字处理:确保正确解析完整的数字,而不是单个字符
- 循环索引:内层数字解析循环后需要回退索引
- 符号初始化:进入新括号时要重置符号为正
扩展应用
- 这种栈的状态保存技巧可应用于:
- 表达式求值类问题
- 括号匹配问题
- 递归结构的迭代实现
相关问题
- LeetCode 227: 基本计算器 II(处理乘除运算)
- LeetCode 772: 基本计算器 III(同时处理加减乘除和括号)
- LeetCode 394: 字符串解码(类似的栈状态保存思路)