【码道初阶】【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. 操作数的顺序性:在非交换律运算(减、除)中,谁先出栈,谁是右操作数。

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

相关推荐
lzhdim10 分钟前
C#不为人知的10个魔法特性:资深开发者也会震惊的底层奥秘
java·前端·javascript·算法·c#
weixin_3077791316 分钟前
C++代码实现MATLAB中的pade函数功能
开发语言·c++·算法·matlab
smj2302_7968265218 分钟前
解决leetcode第4064题至多一次取反能被k整除的最长子数组II
python·算法·leetcode
AIGC小尼22 分钟前
ACE-Step-1.5 本地 AI 音乐模型实战|text2music 歌词转人声,部署脚本、参数调优与排坑全记录
人工智能·算法·comfyui·ai漫剧
ProcessOn官方账号23 分钟前
java IO操作 你快速认识IO的相关基本知识
java·开发语言
YOLO数据集集合29 分钟前
树木检测数据集 | 树木检测 树种分类 航拍林业 森林监测9131期
人工智能·算法·机器学习·分类·数据挖掘·林业·树种分类
计算机毕设定制辅导-无忧学长32 分钟前
《基于Spring Boot的减脂轻食购物网站》
java·vue.js·spring boot·后端·减脂轻食购物网站
青山木34 分钟前
Hot 100 --- 下一个排列
java·数据结构·算法·leetcode
Thinker QAQ35 分钟前
并发编程(六):Atomic 的实现——从 Runtime 到 CPU
java·go·cas·并发编程·atomic·cpython
垆边人似月.38 分钟前
字符串重排后最大字典序
算法·leetcode·职场和发展