当前:LC150 · 逆波兰表达式求值 · 首次出现于 Day 13 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC150 · Evaluate Reverse Polish Notation · 栈

逆波兰求值:遇运算弹出两个数

后缀把运算符写在两个操作数后面。读到数字就入栈;读到加减乘除,弹出栈顶两个,先出的是右边那个,算完把结果压回去。扫完栈里只剩答案。

从左到右扫 token。数字入栈。遇到 + − * /,弹出 b(栈顶,右操作数)和 a(次顶,左操作数),把 a⊗b 压回。减法和除法方向不能反;整数除法向零截断。时间 O(n),空间 O(n)。

时间 O(n)空间 O(n)结论先行 · 全文约 6 节
导读

给你一个逆波兰表达式的 token 数组,求它的值。token 是整数或 +、−、*、/。除法在两个整数之间,向零截断。保证表达式合法。

主例 ["2","1","+","3","*"] 就是中缀 (2+1)*3,值是 9。另一个常见例子 ["4","13","5","/","+"] 是 4+(13/5)=6。

中缀要括号和优先级。后缀把计算顺序写进 token 的排列里。本文要回答:为什么读到运算符时只看栈顶两个,以及弹出顺序反了会怎样。

运算符后到,操作数一定已经在栈里

中缀 2+1 写成后缀 2 1 +:两个数先出现,加号后出现。(2+1)*3 写成 2 1 + 3 *:先把 2 和 1 交给加号,得到的 3 再和后面的 3 交给乘号。谁先算完,由 token 顺序决定,不需要括号,也不需要一张优先级表。

读到运算符时,它的两个操作数就是「最近还没用掉的两个值」。最近,正是栈顶。缺的不是「怎么算加减乘除」,而是一个结构记住这些还没用掉的值。队列不行:先入的 2 会先出,加号拿到的顺序反了。栈后入先出,后写进去的 1 先出来当右操作数,2 留下当左操作数。

每个中间结果算完立刻压回,当作后面某个运算符的操作数。主例加完得到 3,这个 3 不再区分「它是算出来的还是读进来的」,对后面的乘号都一样。

先出的是右操作数后缀 2 1 − 表示 2−1,不是 1−2。栈顶是后读入的 1,必须先弹成 b,再弹 a。除法同样。

数字进栈,符号弹两个再压回

从左到右扫。token 是数字,转成整数压栈。token 是运算符,取栈顶当 b、次顶当 a,弹出这两个,按符号算 a⊗b,结果压回去。扫完栈里只剩一个数,就是答案。题目保证合法,不必处理栈空或栈里剩多个数。

用手走主例。读 2,栈 [2]。读 1,栈 [2,1]。读到 +:b=1,a=2,2+1=3,栈 [3]。读 3,栈 [3,3]。读到 *:b=3,a=3,3*3=9,栈 [9]。演示五帧就是这五步。若把弹出顺序写成先 a 后 b,主例加减碰巧还能蒙对加法,但 4 13 5 / + 会算成 5/13 再加 4,错。

减法、除法看方向:a−b、a/b。Go 对整数 / 就是向零截断,和题目一致,例如 13/5=2,(−3)/2=−1。不要用向负无穷取整的语言习惯去改。乘加没有方向问题,但仍按同一套弹两个。

2 1 + 3 * 的栈演算
21+3*
栈(栈顶在右)
2
数字 2 入栈

Go:栈求值

solution.goGo
func evalRPN(tokens []string) int {
stack := []int{}
for _, t := range tokens {
if t == "+" || t == "-" || t == "*" || t == "/" {
b, a := stack[len(stack)-1], stack[len(stack)-2]
stack = stack[:len(stack)-2]
switch t {
case "+": stack = append(stack, a+b)
case "-": stack = append(stack, a-b)
case "*": stack = append(stack, a*b)
case "/": stack = append(stack, a/b)
}
} else {
v, _ := strconv.Atoi(t)
stack = append(stack, v)
}
}
return stack[0]
}

1栈只存还没用掉的整数,包括中间结果。

2b 取栈顶、a 取次顶,一次切掉两个。先写反就变成 b−a。

3减除用 a⊗b。Go 整数除法向零截断,不用再调。

4数字 atoi 后入栈。扫完只看 stack[0]。

总结

数字入栈,运算符弹两个:先出的是右操作数,算完压回。主例得到 9。

  • 后缀顺序已经编码了计算次序,不需要括号。
  • 2 1 − 是 2−1。栈顶必须当 b。
  • 13/5=2,向零截断。中间结果压回后和其他数字无区别。
同族题目
LC20有效的括号(同为栈应用)LC155最小栈LC224基本计算器