题目描述
思路解析动画文字版
朴素想法提醒我们:括号本质是“进入一层新表达式”。如果不用递归,就需要一个栈手动保存外层现场。
不变量:ans 表示当前括号层里已经结算完的部分,num 表示正在读但还没结算的数字,sign 表示这个 num 前面的符号。
初始化:当前层从 0 开始:ans 只存“已经结算”的部分。刚开始还没读任何字符,所以 ans=0、num=0、sign=+1。
读 '1':数字先攒到 num:读到数字时不要立刻加进 ans,因为后面可能还有更多位数字。统一写成 num = num * 10 + int(ch),多位数也能处理。
读空格:不进入任何分支:代码没有专门写 continue。空格不满足任何 if/elif 条件,所以这一轮什么都不做,继续扫描下一个字符。
读 '-':先结算前面的 1:遇到运算符,说明前一个数字结束了。先把 +1 结算进 ans,再把 sign 改成 -1,准备影响后面的括号或数字。
读 '(':保存外层现场:1-(...) 里的括号前是负号。压栈时必须同时保存外层 ans 和外层 sign,否则合上括号时不知道内层结果要整体变负。
读 '2':在括号内攒数字:进入括号后,我们只看当前层表达式 2+3。外层的 1 和负号已经在栈里等着。
读 '+':结算括号内的 2:加号到来,前面的 2 结束。当前括号层 ans 从 0 变成 2,sign 保持 +1,准备读下一个数字。
读 '3':num 变成 3:数字 3 还没有遇到下一个运算符,所以暂时只放在 num 里。
读 ')':先把内层算完整:合上括号前,要先把最后的 num=3 结算进去。否则括号内只会算到 2,漏掉最后一个数字。
弹出 sign:括号结果整体变负:因为这个括号前面是减号,所以括号结果 5 要整体乘以 -1。注意压栈顺序是 ans 后 sign,所以先 pop 出来的是 sign。
弹出外层 ans:接回 1:现在内层已经变成 -5,再加回外层已算好的 1,得到 1-(2+3) = -4。括号这一层合并完成。
扫描结束:别忘了最后一个 num:循环结束后仍然要返回 ans + sign * num。像 "1+2" 这种表达式,最后的 2 直到字符串结束才会被结算。
这套写法能迁移到很多括号题:进入新层先保存旧现场,退出新层时把结果还原回旧现场。
参考代码
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。
易错点
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。再去主线里挑下一道练手。
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题