LeetCode 224困难栈
基本计算器 图解题解
这道题到底在问什么
给你一个字符串表达式 s,请你实现一个基本计算器来计算并返回它的值。不能使用把字符串当数学表达式求值的内置函数,例如 eval()。s 只包含数字、加号、减号、左右括号和空格,表达式保证有效;减号可以作为一元运算,例如 "-1" 和 "-(2+3)" 是有效的。
- 输入
- s = "1 + 1"
- 输出
- 2
- 输入
- s = " 2-1 + 2 "
- 输出
- 3
- 输入
- s = "(1+(4+5+2)-3)+(6+8)"
- 输出
- 23
最优解:一步一步想明白
- 3朴素想法提醒我们:括号本质是“进入一层新表达式”。如果不用递归,就需要一个栈手动保存外层现场。
- 4不变量:ans 表示当前括号层里已经结算完的部分,num 表示正在读但还没结算的数字,sign 表示这个 num 前面的符号。
- 5执行:ans = 0; num = 0; sign = 1; stack = []ans 只存“已经结算”的部分。刚开始还没读任何字符,所以 ans=0、num=0、sign=+1。
- 6执行:num = num * 10 + int(ch)读到数字时不要立刻加进 ans,因为后面可能还有更多位数字。统一写成 num = num * 10 + int(ch),多位数也能处理。
- 7空格不是数字、+、-、(、),本轮自然跳过代码没有专门写 continue。空格不满足任何 if/elif 条件,所以这一轮什么都不做,继续扫描下一个字符。
- 8执行:ans += sign * num; num = 0; sign = -1遇到运算符,说明前一个数字结束了。先把 +1 结算进 ans,再把 sign 改成 -1,准备影响后面的括号或数字。
- 9执行:stack.append(ans); stack.append(sign); ans = 0; sign = 11-(...) 里的括号前是负号。压栈时必须同时保存外层 ans 和外层 sign,否则合上括号时不知道内层结果要整体变负。
- 10执行:num = num * 10 + int(ch)进入括号后,我们只看当前层表达式 2+3。外层的 1 和负号已经在栈里等着。
- 11执行:ans += sign * num; num = 0; sign = 1加号到来,前面的 2 结束。当前括号层 ans 从 0 变成 2,sign 保持 +1,准备读下一个数字。
- 12执行:num = num * 10 + int(ch)数字 3 还没有遇到下一个运算符,所以暂时只放在 num 里。
- 13执行:ans += sign * num; num = 0合上括号前,要先把最后的 num=3 结算进去。否则括号内只会算到 2,漏掉最后一个数字。
- 14执行:ans *= stack.pop()因为这个括号前面是减号,所以括号结果 5 要整体乘以 -1。注意压栈顺序是 ans 后 sign,所以先 pop 出来的是 sign。
- 15执行:ans += stack.pop()现在内层已经变成 -5,再加回外层已算好的 1,得到 1-(2+3) = -4。括号这一层合并完成。
- 16执行:return ans + sign * num循环结束后仍然要返回 ans + sign * num。像 "1+2" 这种表达式,最后的 2 直到字符串结束才会被结算。
- 19这套写法能迁移到很多括号题:进入新层先保存旧现场,退出新层时把结果还原回旧现场。
⚠️ 容易写错的地方
✗ 错:遇到 + 或 - 只改 sign,不先结算 num
✓ 对:先 ans += sign * num,再 num = 0,再改 sign
运算符说明前一个数字已经结束,必须先把它放进当前层答案。
✗ 错:遇到 ( 只压 ans,不压 sign
✓ 对:同时压入外层 ans 和括号前 sign
像 1-(2+3) 这种表达式,括号结果要整体乘以 -1。
✗ 错:遇到 ) 后先弹 ans 再弹 sign
✓ 对:先弹 sign,再弹外层 ans
代码压栈顺序是 ans 后 sign,栈顶先出来的是 sign。
✗ 错:循环结束直接 return ans
✓ 对:return ans + sign * num
最后一个数字可能还留在 num 里,例如 "1+2"。
完整代码(Python)
Python
class Solution:
def calculate(self, s):
ans = 0
num = 0
sign = 1
stack = []
for ch in s:
if ch.isdigit():
num = num * 10 + int(ch)
elif ch == '+':
ans += sign * num
num = 0
sign = 1
elif ch == '-':
ans += sign * num
num = 0
sign = -1
elif ch == '(':
stack.append(ans)
stack.append(sign)
ans = 0
sign = 1
elif ch == ')':
ans += sign * num
num = 0
ans *= stack.pop()
ans += stack.pop()
return ans + sign * num复杂度
时间复杂度
O(n)
从左到右扫描字符串,每个字符只处理一次。
空间复杂度
O(n)
最坏情况下括号层数接近 n,栈里保存每层的 ans 和 sign。
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 基本计算器 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这道题为什么用「栈」,换最直接的暴力解会差在哪?+
栈抓住了本题的结构特征,把暴力解里重复的工作省掉;暴力解通常要多嵌套一层枚举,数据一大就超时。具体对比见上文「暴力解及其卡点」与「最优解逐步推演」两节。
时间复杂度为什么是 undefined?怎么推出来的?+
按上文复杂度小节的推导,时间复杂度为 undefined。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。