栈的应用场景:判断括号合法性、利用栈完成四则运算等
LeetCode 394. 字符串解码(处理括号表达式)
LeetCode 224. 基本计算器(用栈实现加减运算+处理括号表达式)
LeetCode 227. 基本计算器 II(用栈实现乘除法的优先运算)
例题
LeetCode 394. 字符串解码
根据表达式写出字符串,例如s = "2[a2[c]]d"
,返回"accaccd"
思路:
- 最直接的想法:每次遇到左括号,都寻找与之匹配的右括号,整体计算表达式值
每个[]
括号内的部分视为一个表达式,solve函数每次处理一个表达式并返回结果,如果其内部还有[]
表达式,则递归调用返回
这样做的问题在于,每次要找出与'['
匹配的那个']'
,需要用到栈,并且在递归解决内部子问题的时候,又一次的重复遍历了内部的[]
表达式 - 如何遍历一次解决问题:
函数定义变为:计算从当前指针i开始的第一个右括号内的值
每次遇到[
,直接调用函数解决
每次遇到]
,就返回当前函数的结果
最终,i只需遍历一次表达式,即可获得结果(i指针在所有函数调用中共享)
class Solution:
def decodeString(self, s: str) -> str:
"""递归解法,优化效率:并不每次遇到左括号,都寻找与之匹配的右括号,整体计算值
函数定义变为:计算从当前指针i开始的第一个右括号内的值,最终自动从最内侧的结果向上递归
最终,i只需遍历一次表达式,即可获得结果(i指针在所有函数调用中共享)"""
L = len(s)
i = 0
def solve():
"""计算从当前指针i开始的第一个右括号内的值"""
nonlocal i
res = ''
while i < L:
if s[i].isalpha():
# 普通字符子串,直接拼接
j = i
while j < L and s[j].isalpha():
j += 1
res += s[i:j]
i = j
elif s[i].isdigit():
# 数字+左括号,递归计算子问题
j = s.find('[', i)
combo = int(s[i:j])
i = j + 1
res += combo * solve()
elif s[i] == ']':
# 右括号,返回当前函数的stk中的值,这是某一个括号表达式的结果
i += 1
return res
# print(stk)
return res
return solve()
LeetCode 726. 原子的数量
根据化学式统计其中的各个原子数量,例如"K4(ON(SO3)2)2"
,返回"K4N2O14S4"
思路:
类似上一题,但是这里的()
后面如果为2,则原来化学式内部的所有原子数都要乘二,另外不同化学式的原子数量还要合并,因此使用计数器Counter()
解决这题,合并操作用Counter() + Counter()
即可方便的完成
from collections import Counter
class Solution:
def countOfAtoms(self, formula: str) -> str:
def solve() -> Counter:
"""处理一个表达式,例如K2,(H2O)2
处理范围:当前的i位置到下一个与之配对的括号
利用Counter的+运算合并多个表达式"""
nonlocal i
res = Counter()
while i < L:
if formula[i] == '(':
i += 1
res += solve()
elif formula[i] == ')':
num = 0
while i + 1 < L and formula[i + 1].isdigit():
num *= 10
num += int(formula[i + 1])
i += 1
if num != 0: # ()表达式后有数字下标
for k in res:
res[k] *= num
return res
else: # 单个字母
elem = formula[i]
while i + 1 < L and formula[i + 1].islower():
elem += formula[i + 1]
i += 1
num = 0
while i + 1 < L and formula[i + 1].isdigit():
num *= 10
num += int(formula[i + 1])
i += 1
res[elem] += (1 if num == 0 else num) # 无数字下标,记一次,有下标,记录多次
i += 1
return res
L = len(formula)
i = 0
cnt = solve()
# print(cnt)
ans = ''
for k in sorted(cnt.keys()):
ans += k
if cnt[k] != 1:
ans += str(cnt[k])
return ans
LeetCode 227. 基本计算器 II
给出表达式"1+2*(2+1)"
,返回7
前两题不一定要用到栈,而这题的四则运算特性与栈完美切合:
- 如何实现加减,使用栈,把数字拆分为一个个正、负数入栈,最后计算总和
- 如何实现乘除法优先计算:遇到乘除法,直接用栈顶元素与之计算
- 如何计算括号:每次遇到左括号,计算从当前指针i开始的第一个右括号内的值,最终从最内侧的结果向上递归(与上面两题的方式类似)
class Solution:
def calculate(self, s: str) -> int:
"""实现四则运算和括号优先运算的计算器"""
s = s.replace(" ", "") # 取出所有空格
i = 0
L = len(s)
def calculateSection(s):
"""1 如何实现加减,使用栈,把数字拆分为一个个正、负数入栈,最后计算总和
2 如何实现乘除法优先计算:遇到乘除法,直接用栈顶元素与之计算
3 如何计算括号:优化效率:并不每次遇到左括号,都寻找匹配的右括号,整体计算值
函数定义变为:计算从当前指针i开始的第一个右括号内的值,最终自动从最内侧的结果向上递归
最终,i只需遍历一次表达式,即可获得结果
"""
nonlocal i, L
stack = [] # 利用栈计算各项数字的和
sign = '+' # 第一个正数的符号默认为+号
num = 0 # 当前要入栈的数字
while i < L:
# 下一个"数字"可能是整个括号表达式
if s[i] == '(':
i += 1
num = calculateSection(s) # 计算括号内的值,如果内部还有括号,递归计算
# 正常情况的下一个数字
while i < L and s[i].isdigit():
num = num * 10 + int(s[i])
i += 1
# 当前数字入栈
if sign == '+':
stack.append(num)
elif sign == '-':
stack.append(-num)
elif sign == '*':
stack.append(stack.pop() * num)
elif sign == '/': # 注意,3//2=1,而-3//2=-2
top = stack.pop()
result = top // num if top >= 0 else -(abs(top) // num)
stack.append(result)
# 此时i位于非数字上/结尾处
if i < L:
# 遇到右括号,直接返回括号内的计算结果
if s[i] == ')':
i += 1
break
# 准备计算下一个符号和数字
sign = s[i]
num = 0
i += 1
return sum(stack)
return calculateSection(s)