逆波兰表达式求值 图解题解
逆波兰表达式看起来没有括号、顺序怪,其实它天生就是为栈量身定制的。
像排队取快递再合并:数字token一个个压进栈等待;遇到运算符,就从栈顶取出最近放进来的两个数(先弹的是右操作数、再弹的是左操作数),当场运算,结果压回去继续排队。后进先出恰好和逆波兰的「先写操作数、再写运算符」配合得天衣无缝,扫一遍结束,栈里剩下的就是答案。
这道题到底在问什么
- tokens
- ["2","1","+","3","*"]
- 输出
- 9
最优解:一步一步想明白
- 3逆波兰本来就是为栈设计的。没必要先补括号,再解析表达式树。
- 4遇到数字先放着。遇到运算符,弹出右操作数 b,再弹出左操作数 a,算完结果压回栈。
- 5数字 2 入栈,等待后面的运算符。
- 6数字 1 入栈。现在栈顶两个数是 2 和 1。
- 7遇到加号。栈顶 1 是最近入栈的,先把它弹出来当右操作数 b。
- 81 已经出栈。再弹出栈顶 2 当左操作数 a,计算 a+b = 2+1 = 3。
- 9把结果 3 压回栈。两个数变成一个数——这就是一次运算的净效果。
- 10数字 3 入栈。栈里现在是上一轮结果 3 和新数字 3。
- 11遇到乘号。先弹出栈顶的 3 当右操作数 b。
- 12右操作数 3 已出栈。再弹出最后的 3 当左操作数 a,计算 a*b = 3*3 = 9,此刻栈空。
- 13把结果 9 压回栈。
- 14所有 token 扫完,栈里唯一的数就是答案。
- 17注意弹出顺序:先弹的是右操作数 b。
- 20tokens=["4","2","-"]。遇到减号,栈顶的 2 是最近入栈的,先被弹出当右操作数 b。
- 212 已出栈。再弹出 4 当左操作数 a,计算 a-b = 4-2 = 2,不是 2-4。先弹的是右操作数,顺序千万别反。
- 28这题学完不要乱跳,下一步去 栈专题 连刷同类题;卡住时用 AI 答疑追问“为什么这一步成立”,比只背答案更稳。
⚠️ 容易写错的地方
✗ 错:减法和除法写成 b-a、b/a
✓ 对:先弹 b,再弹 a,计算 a-b、a/b
栈顶是右操作数。
✗ 错:Python 用 // 做除法
✓ 对:用 int(a / b) 向 0 截断
LeetCode 要向 0 截断,不是向下取整。
✗ 错:把负数 token 当运算符
✓ 对:用 token 是否在四个操作符里判断
"-11" 是数字,不是减号。
✗ 错:用 t not in '+-*/' 不点破前提
✓ 对:明白它是子串判断,靠 LC 保证 token 非空;更稳用集合 {"+","-","*","/"}
若 token 为空串 '','' in '+-*/' 为 True,会被误当运算符。
完整代码(Python / C++ / Java)
Python
class Solution:
def evalRPN(self, tokens):
st = []
for t in tokens:
if t not in '+-*/': # 子串判断,靠 LC 保证 token 非空
st.append(int(t))
else:
b = st.pop()
a = st.pop()
if t == '+': st.append(a + b)
elif t == '-': st.append(a - b)
elif t == '*': st.append(a * b)
else: st.append(int(a / b))
return st[-1]C++
class Solution {
public:
int evalRPN(vector<string>& tokens) {
vector<int> st;
for (string& t : tokens) {
if (t != "+" && t != "-" && t != "*" && t != "/") {
st.push_back(stoi(t));
} else {
int b = st.back(); st.pop_back();
int a = st.back(); st.pop_back();
if (t == "+") st.push_back(a + b);
else if (t == "-") st.push_back(a - b);
else if (t == "*") st.push_back(a * b);
else st.push_back(a / b);
}
}
return st.back();
}
};Java
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> st = new ArrayDeque<>();
for (String t : tokens) {
if (t.length() > 1 || !"+-*/".contains(t)) {
st.push(Integer.parseInt(t));
} else {
int b = st.pop(), a = st.pop();
if (t.equals("+")) st.push(a + b);
else if (t.equals("-")) st.push(a - b);
else if (t.equals("*")) st.push(a * b);
else st.push(a / b);
}
}
return st.peek();
}
}套路模板 · 逆波兰/表达式求值骨架
# 表达式求值骨架:操作数攒着,遇运算符就「定量结算」
st = []
for tok in tokens:
if 是操作数(tok):
st.append(转成数(tok)) # 先攒着,等运算符
else: # 二元运算符:恰好弹 2 个
b = st.pop() # 先弹 = 右操作数
a = st.pop() # 后弹 = 左操作数
st.append(apply(tok, a, b)) # a op b 压回
return st[-1] # 合法表达式只剩 1 个vector<long> st;
for (auto& tok : tokens) {
if (isOperand(tok)) {
st.push_back(toNumber(tok)); // 先攒着
} else { // 二元运算符:恰好弹 2 个
long b = st.back(); st.pop_back(); // 先弹 = 右
long a = st.back(); st.pop_back(); // 后弹 = 左
st.push_back(apply(tok, a, b)); // a op b
}
}
return st.back();Deque<Long> st = new ArrayDeque<>();
for (String tok : tokens) {
if (isOperand(tok)) {
st.push(toNumber(tok)); // 先攒着
} else { // 二元运算符:恰好弹 2 个
long b = st.pop(); // 先弹 = 右
long a = st.pop(); // 后弹 = 左
st.push(apply(tok, a, b)); // a op b
}
}
return st.peek();复杂度
时间复杂度
O(n)
每个 token 只处理一次
空间复杂度
O(n)
最坏全是数字先入栈
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 逆波兰表达式求值 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么逆波兰适合栈?+
运算符总是消费最近的两个尚未合并的操作数,正好符合后进先出。
这道题为什么用「栈」,换最直接的暴力解会差在哪?+
栈抓住了本题的结构特征,把暴力解里重复的工作省掉;暴力解通常要多嵌套一层枚举,数据一大就超时。具体对比见上文「暴力解及其卡点」与「最优解逐步推演」两节。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。