LeetCode 剑指 Offer 31. 栈的压入、弹出序列
题目描述

题意分析
给定两个长度相同、元素互不相同的序列
pushed和popped,popped是pushed的一个排列。问:对一个初始为空的容器,按pushed给定的先后顺序放入元素,能否恰好按popped给定的先后顺序把元素取出。能则返回true,否则返回false。题目给的是一个「后进先出」的容器:任何时刻只能取出最后放进去的那个元素。这条约束是全部难度的来源——放入的顺序被
pushed写死了,我们唯一能决定的自由度只有「什么时候取」:每一步要么再放一个进去,要么把当前最上面的那个取出来。约束信号:两个序列长度相同且互为排列,说明每个元素恰好放入一次、取出一次,总操作数固定为
2n;元素互不相同,说明「当前最上面的元素是否就是下一个要取的」这个判断没有歧义。长度上限只有 1000,规模很小,可以放心地把整个过程原样跑一遍。边界情况:两个序列都为空时应返回
true;popped与pushed完全相同(每放一个立刻取一个);popped与pushed完全相反(全部放完再依次取出);以及某个需要取出的元素被压在别的元素下面导致永远取不到。
解法:辅助栈模拟
核心思路
压栈顺序已经由
pushed固定,唯一需要判断的是每个元素应在何时弹出。用辅助栈重放过程:依次压入元素,每次压入后,只要栈顶等于popped中下一个待弹出的值,就连续弹出。这个策略没有遗漏其他可能:栈顶若等于下一个目标,现在就必须弹出;如果继续压入,新元素会挡在它上面,却又必须在目标元素之后弹出,产生矛盾。栈顶若不等于目标,则当前不能弹,只能继续压入。
不变量:指针
j之前的元素已按popped顺序弹出;辅助栈自底向上保存所有已压入但尚未弹出的元素。模拟结束时,j == popped.length当且仅当整个出栈序列可实现。
解题步骤
- 创建空栈,令
j = 0指向下一个待匹配的出栈元素。- 按顺序遍历
pushed,将当前元素压栈。- 当栈非空且栈顶等于
popped[j]时,弹出栈顶并令j++;必须用while处理连续弹出。- 所有元素压入完成后,判断
j是否到达popped末尾。例如
pushed = [1,2,3,4,5]、popped = [4,5,3,2,1]:压入 4 后弹出 4;压入 5 后会连续弹出5、3、2、1,最终全部匹配,返回true。
代码实现
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Deque<Integer> stack = new ArrayDeque<>();
int j = 0;
for (int value : pushed) {
stack.push(value);
while (!stack.isEmpty()
&& j < popped.length
&& stack.peek() == popped[j]) {
stack.pop();
j++;
}
}
return j == popped.length;
}
}
func validateStackSequences(pushed []int, popped []int) bool {
stack := make([]int, 0, len(pushed))
j := 0
for _, value := range pushed {
stack = append(stack, value)
for len(stack) > 0 &&
j < len(popped) &&
stack[len(stack)-1] == popped[j] {
stack = stack[:len(stack)-1]
j++
}
}
return j == len(popped)
}
复杂度分析
- 时间复杂度:$O(n)$。每个元素最多压栈一次、弹栈一次,内层循环的总次数不超过 $n$。
- 空间复杂度:$O(n)$,辅助栈最坏保存全部元素。
关键点总结
- “匹配就立即弹出”是由目标顺序强制决定的,不是需要比较收益的贪心选择。
- 一次压栈可能触发多次弹栈,因此使用
while而不是if。j表示已成功匹配的出栈元素个数,最终用它判断序列是否合法。- Java 的
push / peek / pop要成套使用,避免误用队列方向。
易错点总结
- 内层只写
if:会漏掉一次压栈后连续弹出多个元素的情况。- 先把所有元素压栈再比较:合法序列可能要求压入过程中穿插弹出,会被误判。
- 忘记检查栈为空或
j越界:继续读取栈顶或popped[j]会抛异常。- 弹栈后忘记
j++:匹配进度不会前进,后续判断全部错误。- 混用
addLast与peekFirst:会把栈模拟成队列,破坏后进先出顺序。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 946. 验证栈序列 | 中等 | 与本题完全同题,可直接复用同一份模拟代码 |
| 20. 有效的括号 | 简单 | 栈顶配对即弹,考的是匹配规则而非出栈时机 |
| 剑指 Offer 30. 包含min函数的栈 | 简单 | 辅助栈同步维护历史最小值,考数据结构设计 |
| 剑指 Offer 09. 用两个栈实现队列 | 简单 | 双栈倒腾把后进先出改造成先进先出,复杂度靠摊还分析 |
| 150. 逆波兰表达式求值 | 中等 | 遇操作数入栈、遇运算符弹两个,栈承载的是中间结果 |
| 71. 简化路径 | 中等 | 用栈处理 .. 回退,难点在分段与边界规则 |
| 剑指 Offer 33. 二叉搜索树的后序遍历序列 | 中等 | 同为序列合法性判定,改用递归分治或单调栈 |