leetcode-master/problems/0150.逆波兰表达式求值.md

492 lines
16 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 这不仅仅是一道好题,也展现出计算机的思考方式
# 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 + * 也可以依据次序计算出正确结果。
* 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)[栈的最后表演! | LeetCode150. 逆波兰表达式求值](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<string>& tokens) {
// 力扣修改了后台测试数据需要用longlong
stack<long long> st;
for (int i = 0; i < tokens.size(); i++) {
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
long long num1 = st.top();
st.pop();
long long num2 = st.top();
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(num2 / num1);
} else {
st.push(stoll(tokens[i]));
}
}
int result = st.top();
st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
return result;
}
};
```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
### 题外话
我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。
例如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<Integer> 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();
}
}
```
### Python3
```python
from operator import add, sub, mul
class Solution:
op_map = {'+': add, '-': sub, '*': mul, '/': lambda x, y: int(x / y)}
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token not in {'+', '-', '*', '/'}:
stack.append(int(token))
else:
op2 = stack.pop()
op1 = stack.pop()
stack.append(self.op_map[token](op1, op2)) # 第一个出来的在运算符后面
return stack.pop()
```
另一种可行但因为使用eval相对较慢的方法:
```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()) # 如果一开始只有一个数,那么会是字符串形式的
```
### 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 { // 如果err不为nil说明不是数字
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
var evalRPN = function (tokens) {
const stack = [];
for (const token of tokens) {
if (isNaN(Number(token))) { // 非数字
const n2 = stack.pop(); // 出栈两个数字
const n1 = stack.pop();
switch (token) { // 判断运算符类型,算出新数入栈
case "+":
stack.push(n1 + n2);
break;
case "-":
stack.push(n1 - n2);
break;
case "*":
stack.push(n1 * n2);
break;
case "/":
stack.push(n1 / n2 | 0);
break;
}
} else { // 数字
stack.push(Number(token));
}
}
return stack[0]; // 因没有遇到运算符而待在栈中的结果
};
```
### 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<string, (a: number, b: number) => 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()!;
};
```
### 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<int> stack = new Stack<int>();
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;$i<count($tokens);$i++){
// 是数字直接入栈
if(is_numeric($tokens[$i])){
$st->push($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()
}
}
```
### Rust:
```rust
impl Solution {
pub fn eval_rpn(tokens: Vec<String>) -> i32 {
let mut stack = vec![];
for token in tokens.into_iter() {
match token.as_str() {
"+" => {
let a = stack.pop().unwrap();
*stack.last_mut().unwrap() += a;
}
"-" => {
let a = stack.pop().unwrap();
*stack.last_mut().unwrap() -= a;
}
"*" => {
let a = stack.pop().unwrap();
*stack.last_mut().unwrap() *= a;
}
"/" => {
let a = stack.pop().unwrap();
*stack.last_mut().unwrap() /= a;
}
_ => {
stack.push(token.parse::<i32>().unwrap());
}
}
}
stack.pop().unwrap()
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>