参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

> 这不仅仅是一道好题,也展现出计算机的思考方式 # 150. 逆波兰表达式求值 [力扣题目链接](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) 根据 逆波兰表示法,求表达式的值。 有效的运算符包括 + ,  - ,  * ,  / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 说明: 整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。 示例 1: * 输入: ["2", "1", "+", "3", " * "] * 输出: 9 * 解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9 示例 2: * 输入: ["4", "13", "5", "/", "+"] * 输出: 6 * 解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 示例 3: * 输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"] * 输出: 22 * 解释:该算式转化为常见的中缀算术表达式为: ``` ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22 ``` 逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。 逆波兰表达式主要有以下两个优点: * 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。 * 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。 # 思路 《代码随想录》算法视频公开课:[栈的最后表演! | LeetCode:150. 逆波兰表达式求值](https://www.bilibili.com/video/BV1kd4y1o7on),相信结合视频在看本篇题解,更有助于大家对本题的理解。 在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)提到了 递归就是用栈来实现的。 所以**栈与递归之间在某种程度上是可以转换的!** 这一点我们在后续讲解二叉树的时候,会更详细的讲解到。 那么来看一下本题,**其实逆波兰表达式相当于是二叉树中的后序遍历**。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。 但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后续遍历的方式把二叉树序列化了,就可以了。 在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中的对对碰游戏是不是就非常像了。** 如动画所示: ![150.逆波兰表达式求值](https://code-thinking.cdn.bcebos.com/gifs/150.逆波兰表达式求值.gif) 相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)是差不错的,只不过本题不要相邻元素做消除了,而是做运算! C++代码如下: ```CPP class Solution { public: int evalRPN(vector& tokens) { stack st; for (int i = 0; i < tokens.size(); i++) { if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") { int num1 = st.top(); st.pop(); int num2 = st.top(); st.pop(); if (tokens[i] == "+") st.push(num2 + num1); if (tokens[i] == "-") st.push(num2 - num1); if (tokens[i] == "*") st.push((long)num2 * (long)num1); //力扣改了后台测试数据 if (tokens[i] == "/") st.push(num2 / num1); } else { st.push(stoi(tokens[i])); } } int result = st.top(); st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事) return result; } }; ``` ## 题外话 我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。 例如:4 + 13 / 5,这就是中缀表达式,计算机从左到右去扫描的话,扫到13,还要判断13后面是什么运算法,还要比较一下优先级,然后13还和后面的5做运算,做完运算之后,还要向前回退到 4 的位置,继续做加法,你说麻不麻烦! 那么将中缀表达式,转化为后缀表达式之后:["4", "13", "5", "/", "+"] ,就不一样了,计算机可以利用栈里顺序处理,不需要考虑优先级了。也不用回退了, **所以后缀表达式对计算机来说是非常友好的。** 可以说本题不仅仅是一道好题,也展现出计算机的思考方式。 在1970年代和1980年代,惠普在其所有台式和手持式计算器中都使用了RPN(后缀表达式),直到2020年代仍在某些模型中使用了RPN。 参考维基百科如下: > During the 1970s and 1980s, Hewlett-Packard used RPN in all of their desktop and hand-held calculators, and continued to use it in some models into the 2020s. ## 其他语言版本 java: ```Java class Solution { public int evalRPN(String[] tokens) { Deque stack = new LinkedList(); for (String s : tokens) { if ("+".equals(s)) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等 stack.push(stack.pop() + stack.pop()); // 注意 - 和/ 需要特殊处理 } else if ("-".equals(s)) { stack.push(-stack.pop() + stack.pop()); } else if ("*".equals(s)) { stack.push(stack.pop() * stack.pop()); } else if ("/".equals(s)) { int temp1 = stack.pop(); int temp2 = stack.pop(); stack.push(temp2 / temp1); } else { stack.push(Integer.valueOf(s)); } } return stack.pop(); } } ``` Go: ```Go func evalRPN(tokens []string) int { stack := []int{} for _, token := range tokens { val, err := strconv.Atoi(token) if err == nil { stack = append(stack, val) } else { num1, num2 := stack[len(stack)-2], stack[(len(stack))-1] stack = stack[:len(stack)-2] switch token { case "+": stack = append(stack, num1+num2) case "-": stack = append(stack, num1-num2) case "*": stack = append(stack, num1*num2) case "/": stack = append(stack, num1/num2) } } } return stack[0] } ``` javaScript: ```js /** * @param {string[]} tokens * @return {number} */ var evalRPN = function(tokens) { const s = new Map([ ["+", (a, b) => a * 1 + b * 1], ["-", (a, b) => b - a], ["*", (a, b) => b * a], ["/", (a, b) => (b / a) | 0] ]); const stack = []; for (const i of tokens) { if(!s.has(i)) { stack.push(i); continue; } stack.push(s.get(i)(stack.pop(),stack.pop())) } return stack.pop(); }; ``` TypeScript: 普通版: ```typescript function evalRPN(tokens: string[]): number { let helperStack: number[] = []; let temp: number; let i: number = 0; while (i < tokens.length) { let t: string = tokens[i]; switch (t) { case '+': temp = helperStack.pop()! + helperStack.pop()!; helperStack.push(temp); break; case '-': temp = helperStack.pop()!; temp = helperStack.pop()! - temp; helperStack.push(temp); break; case '*': temp = helperStack.pop()! * helperStack.pop()!; helperStack.push(temp); break; case '/': temp = helperStack.pop()!; temp = Math.trunc(helperStack.pop()! / temp); helperStack.push(temp); break; default: helperStack.push(Number(t)); break; } i++; } return helperStack.pop()!; }; ``` 优化版: ```typescript function evalRPN(tokens: string[]): number { const helperStack: number[] = []; const operatorMap: Map number> = new Map([ ['+', (a, b) => a + b], ['-', (a, b) => a - b], ['/', (a, b) => Math.trunc(a / b)], ['*', (a, b) => a * b], ]); let a: number, b: number; for (let t of tokens) { if (operatorMap.has(t)) { b = helperStack.pop()!; a = helperStack.pop()!; helperStack.push(operatorMap.get(t)!(a, b)); } else { helperStack.push(Number(t)); } } return helperStack.pop()!; }; ``` python3 ```python class Solution: def evalRPN(self, tokens: List[str]) -> int: stack = [] for item in tokens: if item not in {"+", "-", "*", "/"}: stack.append(item) else: first_num, second_num = stack.pop(), stack.pop() stack.append( int(eval(f'{second_num} {item} {first_num}')) # 第一个出来的在运算符后面 ) return int(stack.pop()) # 如果一开始只有一个数,那么会是字符串形式的 ``` Swift: ```Swift func evalRPN(_ tokens: [String]) -> Int { var stack = [Int]() for c in tokens { let v = Int(c) if let num = v { // 遇到数字直接入栈 stack.append(num) } else { // 遇到运算符, 取出栈顶两元素计算, 结果压栈 var res: Int = 0 let num2 = stack.popLast()! let num1 = stack.popLast()! switch c { case "+": res = num1 + num2 case "-": res = num1 - num2 case "*": res = num1 * num2 case "/": res = num1 / num2 default: break } stack.append(res) } } return stack.last! } ``` C#: ```csharp public int EvalRPN(string[] tokens) { int num; Stack stack = new Stack(); foreach(string s in tokens){ if(int.TryParse(s, out num)){ stack.Push(num); }else{ int num1 = stack.Pop(); int num2 = stack.Pop(); switch (s) { case "+": stack.Push(num1 + num2); break; case "-": stack.Push(num2 - num1); break; case "*": stack.Push(num1 * num2); break; case "/": stack.Push(num2 / num1); break; default: break; } } } return stack.Pop(); } ``` PHP: ```php class Solution { function evalRPN($tokens) { $st = new SplStack(); for($i = 0;$ipush($tokens[$i]); }else{ // 是符号进行运算 $num1 = $st->pop(); $num2 = $st->pop(); if ($tokens[$i] == "+") $st->push($num2 + $num1); if ($tokens[$i] == "-") $st->push($num2 - $num1); if ($tokens[$i] == "*") $st->push($num2 * $num1); // 注意处理小数部分 if ($tokens[$i] == "/") $st->push(intval($num2 / $num1)); } } return $st->pop(); } } ``` Scala: ```scala object Solution { import scala.collection.mutable def evalRPN(tokens: Array[String]): Int = { val stack = mutable.Stack[Int]() // 定义栈 // 抽取运算操作,需要传递x,y,和一个函数 def operator(x: Int, y: Int, f: (Int, Int) => Int): Int = f(x, y) for (token <- tokens) { // 模式匹配,匹配不同的操作符做什么样的运算 token match { // 最后一个参数 _+_,代表x+y,遵循Scala的函数至简原则,以下运算同理 case "+" => stack.push(operator(stack.pop(), stack.pop(), _ + _)) case "-" => stack.push(operator(stack.pop(), stack.pop(), -_ + _)) case "*" => stack.push(operator(stack.pop(), stack.pop(), _ * _)) case "/" => { var pop1 = stack.pop() var pop2 = stack.pop() stack.push(operator(pop2, pop1, _ / _)) } case _ => stack.push(token.toInt) // 不是运算符就入栈 } } // 最后返回栈顶,不需要加return关键字 stack.pop() } } ``` -----------------------