力扣(逆波兰表达式求值)
解析 LeetCode 150. 逆波兰表达式求值:栈的精准运算实践
一、题目分析
(一)问题定义
给定逆波兰表达式的字符串数组 tokens
,计算表达式的值。需遵循规则:
- 运算符为
+
、-
、*
、/
。 - 除法向零截断(如
7 / -2
结果为-3
,-7 / 2
结果为-3
)。 - 输入合法,无除零运算。
(二)核心挑战
利用栈实现逆波兰表达式的计算:遇到操作数入栈,遇到运算符则弹出栈顶两个元素(注意顺序 ),运算后结果入栈,最终栈中剩余元素即为结果。
二、算法思想:栈驱动的运算流程
(一)核心思路
逆波兰表达式的计算天然适配栈结构:
- 遍历 tokens:逐个处理字符串。
- 操作数入栈:若字符串是数字(或负号开头的数字 ),转换为整数入栈。
- 运算符处理:若字符串是运算符,弹出栈顶两个元素(注意先弹出的是右操作数,后弹出的是左操作数 ),按运算符规则计算,结果入栈。
- 结果输出:遍历结束后,栈中唯一元素即为表达式结果。
(二)关键细节
- 运算符处理顺序:减法和除法中,先弹出的是右操作数,后弹出的是左操作数(如
a - b
,栈中先弹出b
,再弹出a
,计算a - b
)。 - 数字判断优化:通过字符串长度快速筛选运算符(长度为 1 且是运算符字符 ),其余为操作数。
三、代码实现与详细解析
class Solution {public int evalRPN(String[] tokens) {// 用 ArrayDeque 实现栈,效率高于 Stack 类Deque<Integer> stack = new ArrayDeque<>(); for (String token : tokens) { // 先处理长度为1的情况,快速识别运算符if (token.length() == 1) { char c = token.charAt(0);switch (c) {case '+': // 弹出两个操作数,相加后入栈(顺序不影响)stack.push(stack.pop() + stack.pop()); continue;case '-': // 先弹出右操作数 b,再弹出左操作数 a,计算 a - bint b = stack.pop(); stack.push(stack.pop() - b); continue;case '*': // 弹出两个操作数,相乘后入栈(顺序不影响)stack.push(stack.pop() * stack.pop()); continue;case '/': // 先弹出右操作数 b,再弹出左操作数 a,计算 a / bb = stack.pop(); stack.push(stack.pop() / b); continue;}}// 非运算符(操作数),转换为整数入栈stack.push(Integer.parseInt(token)); }// 栈中最后剩余的元素即为结果return stack.pop(); }
}
(一)代码流程拆解
- 栈初始化:使用
ArrayDeque
作为栈结构,相比Stack
类,避免了线程安全带来的额外开销,效率更高。 - 遍历 tokens:
- 运算符判断:若
token
长度为 1,且字符是运算符(+
、-
、*
、/
),进入switch
处理。 - 加法:弹出两个元素相加,结果入栈(加法交换律,顺序不影响 )。
- 减法:先弹出
b
(右操作数 ),再弹出a
(左操作数 ),计算a - b
后入栈。 - 乘法:弹出两个元素相乘,结果入栈(乘法交换律,顺序不影响 )。
- 除法:先弹出
b
(右操作数 ),再弹出a
(左操作数 ),计算a / b
后入栈(注意向零截断 )。 - 操作数处理:若不是运算符,转换为整数入栈。
- 运算符判断:若
- 结果返回:遍历结束后,栈中只剩一个元素,弹出即为表达式结果。
(二)关键逻辑解析
- 运算符处理顺序:减法和除法中,严格区分左右操作数顺序,先弹出的是右操作数,确保计算符合数学规则(如
tokens = ["4", "13", "5", "/"]
,计算4 / 13
?不,实际是13
是右操作数,4
是左操作数?不,原表达式是4 13 5 /
?不,逆波兰表达式是后缀式,正确流程是:遇到4
入栈,13
入栈,5
入栈,遇到/
,弹出5
(右 )和13
(左 ),计算13 / 5 = 2
,结果入栈;再遇到可能的运算符继续。这里代码中减法和除法的处理,严格遵循 “左操作数 - 右操作数”、“左操作数 / 右操作数” 的逻辑。 - 向零截断处理:Java 中整数除法
a / b
本身就会向零截断(如7 / -2 = -3
,-7 / 2 = -3
),无需额外处理,契合题目要求。 - 效率优化:通过
token.length() == 1
快速筛选运算符,减少不必要的Integer.parseInt
调用,提升效率。
四、复杂度分析
(一)时间复杂度
遍历 tokens
数组一次,每个元素的操作(入栈、出栈、运算 )都是 O(1)O(1)O(1) ,总体时间复杂度为 O(n)O(n)O(n) ,n
是 tokens
数组长度。
(二)空间复杂度
栈最多存储 n/2
个操作数(最坏情况,表达式全是操作数,如 ["1", "2", "3", ...]
,但实际逆波兰表达式中操作数和运算符数量有规律,栈空间主要受操作数数量影响 ),空间复杂度为 O(n)O(n)O(n) 。