栈实现计算器操作(中缀表达式、逆波兰表达式、波兰表达式)
好久没有在leetcode上做题,今天随手发现一道经典的数据结构题目,思路很简单,但是实现起来遇到了几处bug,修修改改花了不少时间,也反映了出思考不完善和语法不熟练的问题,在此记录一下。
中缀表达式
中缀记法是一个通用的算术或逻辑公式表示方法, 操作符是以中缀形式处于操作数的中间(例:3 + 4),中缀表达式是人们常用的算术表示方法。
与前缀表达式(例:+ 3 4)或后缀表达式(例:3 4 +)相比,中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。
与前缀或后缀记法不同的是,中缀记法中括号是必需的。计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。
指路:Leetcode 面试题 16.26. 计算器
思路
本题目中没有涉及到括号的运算,简化了难度,只涉及到四个运算符号和空格(注意存在空格!!虽然感觉很没必要…)四个运算符号存在优先级,加减运算级别低于乘除运算,我们首先定义一个优先级函数,用于决定是否弹栈。
接下来考虑几种情景:
- 3+4+…
- 3+4*…
- 3*4-…
1,2,3分别对应的是优先级不变、优先级上升、优先级下降的几种情况。通过观察我们可以发现,如果将数字和运算符号分别存于两个栈中,当3、4和中间的运算符号分别入栈后,如果优先级不变或者下降,我们可以弹出3、4和运算符号,计算后再入栈;然而在2中,优先级上升,4不可以与3进行计算,而是与后面的表达式计算。
从而,我们可以设计如下思路:
- 设置两个栈:数字栈num和符号栈operator
- 遇到数字则压入数字栈
- 遇到符号先判断,如果符号栈顶元素优先级大于或者等于该符号,则开始进行符号栈弹栈,直到栈顶元素小于目前弹出的元素/栈为空
符号栈弹栈的操作为:
弹出数字栈栈顶两个元素和符号栈的栈顶元素,进行计算后将值压入数字栈(注意弹出的两个元素运算的顺序!!! 尤其是减法和除法操作)
进行到这一步,可以发现目前符号栈内最多有两个符号,且优先级从低到高排列。这时候我们再进行又一轮的符号栈弹栈操作,弹出数字栈栈顶元素即可。
遇到的问题
思路看起来很简单,可是实现起来遇到两个考虑不周的问题:
- 空格
不知为何,题目中要求表达式仅包含非负整数,+, - ,*,/ 四种运算符和 空格,一直没有认真审题所以代码总是报错…无语子 - 输入的数字不是个位数,有n位
测试用例:“42“,按照错误的代码,输出的是2…问题出在一直默认是进行10以内加减乘除= =
我的解决方法是设置一个prev变量,记录上一个输入的字符,如果prev是运算符号,则本次输入的数字直接压栈;否则,弹出数字栈顶元素a,将(a*10+本次输入的数字)压栈。
实现起来很简单,在循环开始前设置prev=+(减乘除也可,运算符号就可以),然后循环结束前令prev=i.
代码
一段繁琐且耗时长的python代码实现…
class Solution:
def calculate(self, s: str) -> int:
def priority(n):
if n in ['+','-']:
return 1
else:
return 2
stack_num=[]
stack_operator=[]
prev='+'
for i in s:
if i==' ':
continue
else:
if i in ['+','-','*','/']:
if stack_operator==[]:
stack_operator.append(i)
elif priority(stack_operator[-1])>=priority(i):
while stack_operator and priority(stack_operator[-1])>=priority(i):
a=stack_num.pop()
b=stack_num.pop()
operator=stack_operator.pop()
if operator=='+':
c=a+b
if operator=='-':
c=b-a
if operator=='*':
c=a*b
if operator=="/":
c=int(b/a)
stack_num.append(c)
stack_operator.append(i)
else:
stack_operator.append(i)
else:
if prev in ['+','-','*','/']:
stack_num.append(int(i))
else:
a=stack_num.pop()
stack_num.append(a*10+int(i))
prev=i
while stack_operator:
a=stack_num.pop()
b=stack_num.pop()
operator=stack_operator.pop()
if operator=='+':
c=a+b
if operator=='-':
c=b-a
if operator=='*':
c=a*b
if operator=="/":
c=int(b/a)
stack_num.append(c)
return stack_num[-1]
后记
这一解法的时间复杂度较高,因为每次遇到优先级不变或较低的元素都要进行弹栈/压栈操作,但空间复杂度很低—因为符号栈中最多保留两个元素,如何降低空间复杂度呢?其实这种解法利用的思想可以用中缀表达式转后缀表达式,再根据后缀表达式计算两步来实现。接下来介绍一下逆波兰表达式的转化及计算。