【码道初阶】【LeetCode 150】逆波兰表达式求值:为什么栈是它的最佳拍档?

【LeetCode 150】逆波兰表达式求值:为什么栈是它的最佳拍档?

逆波兰表达式 (Reverse Polish Notation, RPN),也称为后缀表达式,初看之下可能觉得反直觉:运算符竟然跟在数字后面?比如 2 + 1 写成了 2 1 +

但这种表达方式对计算机来说却极其友好:它不需要括号来规定优先级,也不需要复杂的递归,只需要从左到右扫描一遍即可算出结果。

解决这个问题的神器,就是数据结构中的------栈(Stack)

1. 核心逻辑:数字入栈,符号吃栈

逆波兰表达式的计算规则非常简单,可以概括为一句话:遇到数字就存起来,遇到符号就拿最近的两个数字计算。

栈的"后进先出"(LIFO)特性完美契合了这个需求。我们可以把栈想象成一个暂存箱:

  1. 遍历数组 :从左到右依次读取字符串数组 tokens
  2. 遇到数字 :直接压入栈中(Push),等待被后面的运算符"临幸"。
  3. 遇到运算符
    • 从栈中弹出(Pop)两个数字
    • 根据运算符进行计算。
    • 将计算结果重新压入栈中(Push),作为下一轮计算的操作数。
  4. 结束:遍历完成后,栈里剩下的最后一个元素就是最终结果。

2. 关键细节:左右操作数的顺序

在代码实现中,有一个非常容易出错的细节:出栈顺序与运算顺序的关系

当遇到减法 - 或除法 / 时,顺序至关重要。

栈顶的元素是最后 放进去的,所以在原表达式中,它是运算符右边的那个数。

例如表达式 4 13 5 / +

  1. 栈中有 [4, 13, 5] (5在栈顶)。
  2. 遇到 /
  3. 第一次 Pop 拿到 5 (这是除数/右操作数 val2)。
  4. 第二次 Pop 拿到 13 (这是被除数/左操作数 val1)。
  5. 计算 13 / 5,而不是 5 / 13

更简单的说,第一个弹出栈的就是操作符右边的数,第二个弹出来的就是操作符左边的数

3. 代码深度解析

让我们结合 Java 代码来看看这个过程是如何落地的。

3.1 辅助判断工具

首先,为了代码简洁,封装一个判断当前字符串是否为运算符的方法:

java 复制代码
private boolean isOperator(String s) {
    // 只要是 + - * / 中的一种,就是运算符
    if(s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
        return true;
    }
    return false;
}

3.2 主逻辑流程

java 复制代码
public int evalRPN(String[] tokens) {
    Stack<Integer> stack = new Stack<>();
    
    for(int i = 0; i < tokens.length; i++) {
        // 情况一:如果是数字
        if(!isOperator(tokens[i])) {
            // 将字符串转为整数,直接入栈
            int num = Integer.parseInt(tokens[i]);
            stack.push(num);
        } 
        // 情况二:如果是运算符
        else {
            // 注意弹出顺序!
            // 先出来的是右操作数 (val2)
            int val2 = stack.pop(); 
            // 后出来的是左操作数 (val1)
            int val1 = stack.pop(); 
            
            // 根据符号进行计算
            switch(tokens[i]) {
                case "+":
                    stack.push(val1 + val2);
                    break;
                case "-":
                    stack.push(val1 - val2); // 注意是 val1 - val2
                    break;
                case "*":
                    stack.push(val1 * val2);
                    break;
                case "/":
                    stack.push(val1 / val2); // 注意是 val1 / val2
                    break;
            }
        }
    }
    // 最终栈里只剩下一个结果
    return stack.pop();
}

4. 复杂度分析

  • 时间复杂度 :O(N)O(N)O(N)。我们需要遍历整个 tokens 数组一次,每个元素的操作(入栈、出栈、计算)都是常数时间的。
  • 空间复杂度 :O(N)O(N)O(N)。在最坏的情况下(例如 1 2 3 4 + + +),栈中需要存储接近 N 个数字。

5. 总结

LeetCode 150 是一道经典的栈应用题。它展示了栈在计算机科学中最本质的用途之一:处理具有嵌套结构或顺序依赖的运算

通过这道题,我们不仅学会了如何解析逆波兰表达式,更深刻理解了:

  1. 栈的暂存能力:保存暂时不用的数据。
  2. 操作数的顺序性:在非交换律运算(减、除)中,谁先出栈,谁是右操作数。

掌握了这个模式,以后遇到"后缀表达式求值"或者"基本计算器"类的题目,都能迎刃而解。

相关推荐
想用offer打牌2 小时前
一站式了解长轮询,SSE和WebSocket
java·网络·后端·websocket·网络协议·系统架构
C雨后彩虹2 小时前
最大数字问题
java·数据结构·算法·华为·面试
java修仙传2 小时前
力扣hot100:搜索二维矩阵
算法·leetcode·矩阵
梦里不知身是客112 小时前
tomcat作用和功能以及默认端口号
java·tomcat
码界奇点2 小时前
基于SpringBoot与Vue3的多租户中后台管理系统设计与实现
java·spring boot·后端·spring·车载系统·毕业设计·源代码管理
浅川.252 小时前
xtuoj 字符串计数
算法
长安城没有风2 小时前
在 IntelliJ IDEA 中高效使用 Git 的实用指南
java·git·intellij-idea
Code blocks2 小时前
SpringBoot从0-1集成Netty实现自定义协议开发
java·spring boot·后端
天`南2 小时前
【群智能算法改进】一种改进的金豺优化算法IGJO[1](动态折射反向学习、黄金正弦策略、自适应能量因子)【Matlab代码#94】
学习·算法·matlab