剑指 Offer 31 栈的压入、弹出序列:用模拟栈判定弹出顺序的完整实现与复杂度分析
本文基于 CS-Notes 仓库中的剑指 Offer 题解文档 31. 栈的压入、弹出序列 展开,完整覆盖原题描述、"模拟压入弹出"的解题思路与标准 Java 解法,并补充题目关键假设的解读、两个样例的逐步推演、复杂度分析、边界条件处理以及与仓库内栈数据结构实现的关联,帮助读者彻底掌握这道经典栈模拟题的写法与易错点。
题目描述
原题完整表述(来自文档):
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。
例如序列
1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
这道题的核心是判定可行性,而不是输出具体操作:给定压入序列 pushSequence,问 popSequence 是否恰好是某种合法出入栈操作下得到的弹出结果。
题目中"压入栈的所有数字均不相等"这一假设非常关键:它保证了 popSequence 中的每个元素在 pushSequence 里至多出现一次,使得模拟过程中"当前期望弹出的值"唯一明确。若允许重复数字,匹配会变得多义,判定逻辑需要额外处理,这也是为什么题目要提前给出这个前提。
解题思路:用栈模拟压入弹出
文档给出的思路是:使用一个栈来模拟压入弹出操作。每次入栈一个元素后,都要判断一下栈顶元素是不是当前出栈序列 popSequence 的第一个元素,如果是的话则执行出栈操作并将 popSequence 往后移一位,继续进行判断。
这句话拆解成三步:
- 按顺序压入:外层循环按
pushSequence的下标依次push,不能乱序,因为压入顺序是题目给定的; - 能弹就弹:每压入一个元素后,用
while循环持续检查栈顶是否等于popSequence[popIndex],相等就弹出并把弹出游标popIndex前移,直到栈顶不再匹配或栈已空; - 全部结束后判空:所有元素处理完后,若栈为空说明所有元素都被合法弹出,返回
true;否则说明有元素"卡"在栈里弹不出去,返回false。
这个贪心策略之所以正确,是因为栈的弹出操作具有唯一强制性:在任意时刻,只有"弹栈顶"一种选择,不存在分支决策。因此只要按"栈顶匹配就立刻弹"地模拟,得到的就是该压入序列下唯一确定的弹出过程,最终栈是否为空即判定结果。
标准 Java 解法
完整继承文档中的实现:
public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
int n = pushSequence.length;
Stack<Integer> stack = new Stack<>();
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
stack.push(pushSequence[pushIndex]);
while (popIndex < n && !stack.isEmpty()
&& stack.peek() == popSequence[popIndex]) {
stack.pop();
popIndex++;
}
}
return stack.isEmpty();
}
几个值得注意的实现细节:
- 双下标
pushIndex/popIndex分别代表"下一个待压入元素"和"当前期望弹出元素",整个模拟过程是单趟扫描,不需要回溯; stack.peek() == popSequence[popIndex]这一比较是安全的:popSequence是int[],popSequence[popIndex]为基本类型int,而peek()返回的Integer会因比较对象是int而自动拆箱,最终是比较两个int值,不存在Integer缓存区间导致引用比较的坑;while循环中popIndex < n放在最前面,防止弹出序列已全部消费完后越界访问;- 方法最后
return stack.isEmpty(),等价于"所有元素都被弹出"。
样例推演:为什么 4,5,3,2,1 合法而 4,3,5,1,2 非法
以压入序列 1,2,3,4,5 为例,走一遍上面的算法。
弹出序列 4,5,3,2,1(合法):
| 操作 | 栈(栈顶在右) | popIndex 期望值 |
|---|---|---|
| 依次压入 1、2、3、4 | 1,2,3,4 |
4 |
| 栈顶 4 匹配,弹出 | 1,2,3 |
5 |
| 压入 5 | 1,2,3,5 |
5 |
| 栈顶 5 匹配,弹出 | 1,2,3 |
3 |
| 栈顶 3 匹配,弹出 | 1,2 |
2 |
| 栈顶 2 匹配,弹出 | 1 |
1 |
| 栈顶 1 匹配,弹出 | 空 | 序列结束 |
循环结束时栈为空,返回 true。
弹出序列 4,3,5,1,2(非法):
| 操作 | 栈(栈顶在右) | popIndex 期望值 |
|---|---|---|
| 依次压入 1、2、3、4 | 1,2,3,4 |
4 |
| 栈顶 4 匹配,弹出 | 1,2,3 |
3 |
| 栈顶 3 匹配,弹出 | 1,2 |
5 |
| 压入 5 | 1,2,5 |
5 |
| 栈顶 5 匹配,弹出 | 1,2 |
1 |
此时外层循环已把所有元素压完,但栈中残留 1,2(栈顶是 2),而期望弹出的是 1——2 压在 1 上面,必须先弹出 2 才能弹出 1,与 popSequence 要求的顺序矛盾。循环结束时栈非空,返回 false,与文档结论一致。
复杂度分析
- 时间复杂度 O(n):
n为序列长度。每个元素最多入栈一次、出栈一次,所有push/pop操作总次数不超过2n,每次为 O(1); - 空间复杂度 O(n):最坏情况下所有元素依次入栈后无法弹出(如弹出序列恰好逆序压满),模拟栈最多容纳
n个元素。
边界条件与健壮性补充
文档实现默认两个序列非空且等长(方法开头直接取 pushSequence.length 作为 n)。如果实际调用中可能出现长度不一致或空序列,可以先加防御性判断:
if (pushSequence == null || popSequence == null
|| pushSequence.length != popSequence.length) {
return false;
}
其他典型边界场景:
- 两序列相同(如
1,2,3压1,2,3弹):每压一个就立刻弹出一个,栈始终不深,返回true; - 两序列逆序(如
1,2,3压3,2,1弹):全部压入后从栈顶依次弹出,返回true; - 长度不等:弹出序列不可能与压入序列一一对应,直接判
false。
与仓库内其他内容的关联
- 本题在剑指 Offer 题解索引 剑指 Offer 题解 - 目录 中对应条目为 31. 栈的压入、弹出序列,同系列的栈相关题目还有 包含 min 函数的栈 与 用两个栈实现队列,可以对照阅读;
- 模拟过程依赖的
Stack接口,与仓库 算法 - 栈和队列 中给出的MyStack接口定义(push/pop/isEmpty/size)在语义上完全一致,该文件还提供了数组实现ArrayStack与链表实现两种自研栈。从源码结构看,本题中的new Stack<>()完全可以用该文件里的ArrayStack替换——判定逻辑只用到压入、弹栈、看栈顶和判空四种操作,任何标准栈实现都可支撑这道题,这也反过来验证了"判定算法与具体栈的存储结构无关"。
小结
剑指 Offer 31 的本质是用贪心模拟替代组合枚举:不列举所有可能的出入栈操作序列,而是利用"栈顶匹配就立即弹出"的唯一强制性,单趟扫描完成合法性判定。掌握"每压一个元素后尽可能弹出"的循环骨架(外层按序压入、内层 while 匹配弹出、最后判栈空),是解决这类栈序列判定问题的通用模式。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00