Article / 文章

LeetCode 150 逆波兰表达式求值:栈的经典应用

给你一个字符串数组 tokens,表示一个根据逆波兰表示法表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意: - 有效的算符为 '+'、'-'、'' 和 '/' - 每个操作数(运算对象)都可以是一个整数或者另一个表达式 - 两个整数之间的除法总是向零截断 - 表达式中不含除零运算 - 输入是一个根据逆波兰表示法表示的算术表达式 -

题目描述

给你一个字符串数组 tokens,表示一个根据逆波兰表示法表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式
  • 两个整数之间的除法总是向零截断
  • 表达式中不含除零运算
  • 输入是一个根据逆波兰表示法表示的算术表达式
  • 答案及所有中间计算结果可以用 32 位整数表示

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

输入:tokens = ["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 <= tokens.length <= 10^4
  • tokens[i] 是一个算符("+""-""*""/"),或是在范围 [-200, 200] 内的一个整数

解题思路

方法:栈

逆波兰表达式的特点是操作符在操作数之后,适合用栈来处理。

关键点:

  1. 遇到数字时入栈
  2. 遇到运算符时,从栈中弹出两个操作数进行计算
  3. 计算结果重新入栈
  4. 处理负数的情况

具体步骤:

  1. 遍历tokens数组
  2. 对于每个token:
    • 如果是数字,转换为整数并入栈
    • 如果是运算符,从栈中弹出两个操作数,计算结果并入栈
  3. 最后栈顶元素即为表达式结果

时间复杂度:O(n),其中n是tokens数组的长度。 空间复杂度:O(n),栈中最多存储n/2个数字。

图解思路

以示例1为例,演示计算过程:

  1. 初始状态:
栈:空
  1. 处理“2”:
栈:[2]
  1. 处理“1”:
栈:[2, 1]
  1. 处理”+“:
栈:[3]  // 2 + 1 = 3
  1. 处理“3”:
栈:[3, 3]
  1. 处理”*“:
栈:[9]  // 3 * 3 = 9

代码实现

C# 实现

public class Solution {
    public int EvalRPN(string[] tokens) {
        Stack<int> stack = new Stack<int>();
        
        foreach (string token in tokens) {
            if (IsOperator(token)) {
                int b = stack.Pop();
                int a = stack.Pop();
                stack.Push(Calculate(a, b, token));
            } else {
                stack.Push(int.Parse(token));
            }
        }
        
        return stack.Pop();
    }
    
    private bool IsOperator(string token) {
        return token == "+" || token == "-" || token == "*" || token == "/";
    }
    
    private int Calculate(int a, int b, string op) {
        switch (op) {
            case "+": return a + b;
            case "-": return a - b;
            case "*": return a * b;
            case "/": return a / b;
            default: throw new ArgumentException("Invalid operator");
        }
    }
}

Python 实现

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        
        for token in tokens:
            if token in "+-*/":
                b = stack.pop()
                a = stack.pop()
                stack.append(self.calculate(a, b, token))
            else:
                stack.append(int(token))
                
        return stack.pop()
        
    def calculate(self, a: int, b: int, op: str) -> int:
        if op == "+": return a + b
        if op == "-": return a - b
        if op == "*": return a * b
        if op == "/": return int(a / b)  # 向零截断
        raise ValueError("Invalid operator")

C++ 实现

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        
        for (const string& token : tokens) {
            if (isOperator(token)) {
                int b = st.top(); st.pop();
                int a = st.top(); st.pop();
                st.push(calculate(a, b, token));
            } else {
                st.push(stoi(token));
            }
        }
        
        return st.top();
    }
    
private:
    bool isOperator(const string& token) {
        return token == "+" || token == "-" || token == "*" || token == "/";
    }
    
    int calculate(int a, int b, const string& op) {
        if (op == "+") return a + b;
        if (op == "-") return a - b;
        if (op == "*") return a * b;
        if (op == "/") return a / b;  // 向零截断
        throw invalid_argument("Invalid operator");
    }
};

性能分析

各语言实现的性能对比:

实现语言 执行用时 内存消耗 特点
C# 92 ms 38.2 MB 实现简洁,性能适中
Python 156 ms 16.8 MB 代码最简洁
C++ 24 ms 9.6 MB 性能最优

补充说明

代码亮点

  1. 使用栈实现逆波兰表达式的计算
  2. 处理了除法的向零截断
  3. 代码结构清晰,易于维护

常见错误

  1. 没有处理负数的情况
  2. 除法没有向零截断
  3. 栈操作顺序错误(先弹出的是第二个操作数)

相关题目

讨论

有几个问题可以思考一下:

  1. 逆波兰表达式(后缀表达式)和我们常见的中缀表达式比,有什么优势?
  2. 如果要把中缀表达式转换成逆波兰表达式,应该怎么做?
  3. 除了计算器,栈还能用来解决哪些问题?

欢迎在评论区讨论。


如果你都看到这里了,说明还是有点收获的吧?给个赞鼓励一下呗。