Leetcode
224使用栈和逆波兰式解决此类计算器问题
示例 1:
输入:s = “1 + 1”
输出:2
示例 2:
输入:s = " 2-1 + 2 "
输出:3
示例 3:
输入:s = “(1+(4+5+2)-3)+(6+8)”
输出:23
提示:
1 <= s.length
<= 3 * 105
s 由数字、’+’、’-’、’(’、’)’、和 ’ ’ 组成
s 表示一个有效的表达式
题解:
方式一:栈
由于仅有加减和括号因此,特别是括号,记得当初学数据结构栈时,判断括号就是通过栈这种数据结构进行判断的,因此这题首先想到了用栈。那用栈来存储什么呢?
先从最简单的开始思考:
- 如果遇到
空格
, 一般是用来将数字和运算符或者括号之间才会出现,对数字前的正负没有影响。遍历时可以直接跳过. - 在计算当中难免会遇到数层括号,而括号内的正负,也和括号前的正负有关,括号的层数有关,因此,我们不妨设计一个栈的数据结构用来存储每一层的正负:即
当进入括号时,我们将当前进入括号前的绝对正负添加到栈顶,然后每进入一个括号就将进入括号前的绝对正负添加到栈顶,如果出括号就将栈顶判断正负的数弹出。然后判断正负时,我们便仅需要判断当前遇到的符号为+还是-,判断sign正负由3和4解答
。 - 如果遇到
+
,显然后面的数字的正负只取决于最后存储在栈底的正负有关即sign = pos_or_neg[-1]
. - 遇到
-
时,我们不知道前面有多少个-
号对这个减号起作用,但是我们使用了pos_or_neg
存储在栈顶的 - 然后当遇到数字并且没有超出该字符串的范围时:我们先将这个数判断出来后乘以它的
绝对正负
加到result中,最后结束while循环返回。 - 时间复杂度为
O(n)
,空间复杂度为O(n)
。
class Solution:
def calculate(self, s: str) -> int:
pos_or_neg = [1]
n = len(s)
i = 0
result = 0
sign = 1
while i < n:
if s[i] == ' ':
i += 1
elif s[i] == '+':
i += 1
sign = pos_or_neg[-1]
elif s[i] == '-':
i += 1
sign = - pos_or_neg[-1]
elif s[i] == '(':
pos_or_neg.append(sign)
i += 1
elif s[i] == ')':
pos_or_neg.pop()
i += 1
else:
num = 0
while i< n and s[i].isdigit():
num =10*num + int(s[i])
i += 1
result +=sign*num
return result
方式二 逆波兰式(后缀表达式)
解题思路
采用常规的思路,先将中缀表达式转换成后缀表达式,接着再计算后缀表达式。
中缀表达式转后缀表达式
后缀表达式使用string
存储,转后缀表达式的过程中使用一个操作符栈存储操作符,后缀表达式中数字和操作数之间均有一个空格分隔开。
遍历中缀表达式,空格跳过,遇到数字则读取(注意可能有多位数字,我这里只把负号看作操作符而不是数字前的符号),并加到后缀表达式中。
遇到左括号,压入操作符栈中。
遇到操作符,遵循一条规则——若栈顶的操作符优先级高于或等于当前符号优先级,则不断出栈并加到后缀表达式中,直到栈顶操作符的优先级小于当前符号优先级(’('的优先级最小)。接着将当前操作符入栈。
遇到右括号,不断出栈并加入到后缀表达式中,直到左括号也出栈。
计算后缀表达式
使用一个操作数栈,遍历后缀表达式,遇到数字将其压入栈中,遇到操作符则将栈顶两个操作数弹出并计算,接着压回栈中,结束后操作数栈只剩下一个数字,返回即为结果。
注意:由于我将所有数字视为整数,所以负号看成操作符,但如果类似"-1+3"或"1+(-1+2)"类似的表达式,会有问题,因为减号缺少左操作数,所以我们可以事先在操作数栈中压入一个0,既不影响大小,也解决了特例。
import re
class Solution(object):
def calculate(self, s):
#首先将表达式转换为逆波兰表达式
s=re.sub(' ', '', s) # 去除空格
output=[]
stack=[]
i=0
while i <len(s):
#如果是数字直接输出
if s[i]!='(' and s[i]!=')' and s[i]!='+' and s[i]!='-':
j=i+1
while j<len(s) and s[j]!='(' and s[j]!=')' and s[j]!='+' and s[j]!='-':
j=j+1
output.append(int(s[i:j]))
i=j
continue
#如果是操作符,因为本题只有加减,遇到操作符就将栈内左括号前的操作符全部输出,把当前操作符压栈
if s[i]=='+' or s[i]=='-':
while stack!=[] and stack[-1]!='(':
output.append(stack.pop(-1))
stack.append(s[i])
i=i+1
continue
#如果是左括号就压栈
if s[i]=='(':
stack.append(s[i])
i=i+1
continue
#如果是右括号,就把栈内第一个左括号前的所有操作符输出
if s[i]==')':
while stack!=[] and stack[-1]!='(':
output.append(stack.pop(-1))
stack.pop(-1)
i=i+1
continue
#输出栈内剩余操作符
while stack!=[]:
output.append(stack.pop(-1))
#转换为逆波兰表达式之后,遍历整个表达式,遇到数字就压栈,
#遇到操作符就把栈顶两个数字出栈计算,把计算结果压栈,直到栈内只剩一个数字就是答案
if len(s)>1 and (s[0]=='+' or s[0]=='-' or s[0]=='(' or s[0]==')'):
output=[0]+output
for i in range(len(output)):
if output[i]!='+' and output[i]!='-':
stack.append(output[i])
else:
a=stack.pop(-1)
b=stack.pop(-1)
if output[i]=='+':
stack.append(a+b)
else:
stack.append(b-a)
return stack[-1]
参考文献:
【1】https://leetcode-cn.com/problems/basic-calculator/comments/
【2】https://leetcode-cn.com/problems/basic-calculator/solution/227-ji-ben-ji-suan-qi-ii-c-chang-gui-si-86lqn/