- 教程
- 文档
【免费下载链接】LogicStack-LeetCode
公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码
本篇技术指南以 456. 132 模式(中等) 题解为核心,完整讲解如何利用「逆序遍历 + 单调递减栈」在 $O(n)$ 时间内判定数组中是否存在满足 $nums[i] < nums[k] < nums[j]$($i < j < k$)的三元组。文中从朴素枚举的复杂度瓶颈出发,逐步推导出"维护单调递减栈 + 记录出栈元素最大值k"的核心算法,并给出过程演示、正确性证明以及 Java / C++ / Python3 / TypeScript 四种语言的完整可运行代码,读完后你将掌握一类"借助单调栈把三重枚举压缩为单趟扫描"的经典技巧,并能够直接迁移到 单调栈专题 中的其他同类题目。
一、题目回顾:什么是 132 模式
LeetCode456. 132 模式(中等)要求:给定一个整数数组nums,判断其中是否存在"132 模式"的子序列。
所谓132 模式,即存在三个下标 $i < j < k$,且数值满足:
$$ nums[i] < nums[k] < nums[j] $$
从形状上看,这三个数的相对大小关系构成"1 → 3 → 2"的排列,因此得名。注意这里的子序列不要求连续,只需保持相对顺序即可。
示例解析
示例 1
输入:nums = [1,2,3,4] 输出:false序列单调递增,任意三元组都满足 $nums[i] < nums[j] < nums[k]$,无法凑出"中间最大、末尾次大"的形态,因此不存在 132 模式。
示例 2
输入:nums = [3,1,4,2] 输出:true子序列[1, 4, 2]满足 $1 < 2 < 4$,即 $i=1,\ j=2,\ k=3$ 对应 $nums[1]=1 < nums[3]=2 < nums[2]=4$,命中 132 模式。
示例 3
输入:nums = [-1,3,2,0] 输出:true存在 3 组 132 模式的子序列:[-1, 3, 2]、[-1, 3, 0]和[-1, 2, 0],因此返回true。
数据范围与进阶要求
- $n = nums.length$,且 $1 \le n \le 10^4$
- $-10^9 \le nums[i] \le 10^9$
进阶要求是设计时间复杂度为 $O(n \log n)$ 或 $O(n)$ 的算法——这直接决定了朴素做法不可行,也指明了本题的核心考点在于"如何压缩枚举维度"。
二、朴素思路与复杂度瓶颈
最直接的思路是分别枚举 $i$、$j$、$k$ 三个位置,逐一检查是否满足 $nums[i] < nums[k] < nums[j]$,复杂度为 $O(n^3)$。在 $n$ 最大为 $10^4$ 时,$10^{12}$ 量级的运算在 LeetCode 上必然超时。
更"折中"的做法是枚举其中两个数、再优化第三个数的查找:例如枚举 $i$ 与 $j$,然后在其后查找是否存在位于 $(nums[i], nums[j])$ 区间内的 $nums[k]$。若用哈希表辅助,可将单次查找降为 $O(1)$,整体为 $O(n^2)$。但正如题解原文所指出的:
这样的数据范围甚至不足以我们枚举其中两个数,然后优化找第三个数的 $O(n^2)$ 做法。
$n=10^4$ 时 $O(n^2)$ 意味着 $10^8$ 次运算,逼近多数判题环境的超时临界线,因此 $O(n^2)$ 同样不够稳妥。
题解原文还提到一条替代路线:树状数组。利用树状数组维护值域上的前缀最值/计数,配合值域离散化,可以做到 $O(n \log n)$,能够通过本题。但树状数组方案代码量较大,且需要额外理解离散化等前置知识,并非本题的最优解。真正的亮点在于下面的单调栈 $O(n)$ 做法——它比树状数组更快,代码也更简洁。
三、从 132 的结构特性出发:三种枚举视角
在确定一个数之后,如何快速找到另外两个数?题解从 132 结构中每个位置的"角色"出发,给出了三种分析视角:
枚举
i(1 的角色):i是 132 结构中最小的数,需要从i后面找到一对(j, k),使得两者都大于i,且 $j > k$。由于遍历是单向的,可以转化为:找k,要求k > i,同时保证在[i, k]之间存在比k更大的数。枚举
j(3 的角色):j是 132 结构里最大的数,需要在j的右边找比j小的「最大」的数,在j的左边找比j小的「最小」的数。这很容易联想到单调栈,但朴素单调栈只能帮我们找到左边或右边「最近」的数,无法直接满足「最大」和「最小」的要求,需要引入额外逻辑。枚举
k(2 的角色):k是 132 结构中的中间值,分析逻辑与枚举i类似:遍历是单向的,需要找到k左边的i,同时确保[i, k]之间存在比i和k都大的数字。
三种视角在原理上均可行,但题解明确指出**「枚举i」的做法最简单**,核心原因是一句关键观察:
因为如果存在
(j,k)满足要求的话,我们只需要找到一个最大的满足条件的k,通过与i的比较即可。
也就是说:只要把"在i右侧是否存在合法(j,k)"压缩成一个标量k(右侧可供选择的、满足"有更大元素垫背"的最大值),那么对i的判定就退化为一次简单的大小比较。而"右侧是否存在 $j > k$ 的合法对"恰好是单调栈最擅长维护的信息。
四、核心算法:逆序遍历 + 单调递减栈
4.1 前置知识:单调栈能做什么
单调栈是本题的算法底座。在 单调栈专题 中,仓库收录了 42. 接雨水(困难)、496. 下一个更大元素 I(简单)、503. 下一个更大元素 II(中等)、739. 每日温度(中等) 等一系列同类问题。
单调栈的核心能力是:在 $O(n)$ 时间内,为每个元素找到其某一侧"第一个比它大/小"的元素。以 496 题为例(题解原文):
- 对数组逆序遍历,实时维护一个单调栈;
- 遍历到
nums[i]时,先弹出栈顶所有比nums[i]小的元素; - 若栈空,说明右侧没有更大的元素;否则栈顶即为右侧最近的大于
nums[i]的元素。
本题的关键差异在于:我们不仅需要"最近",更需要把所有被弹出的元素中的最大值保留下来,作为 132 结构中"2"的候选值。
4.2 算法流程
题解的处理过程可以概括为两句话:
我们从后往前遍历,维护一个「单调递减」的栈,同时使用
k记录所有出栈元素的最大值(k代表 132 结构中的 2)。当遍历到某个nums[i] < k时,说明找到了符合条件的i j k。
具体流程如下:
- 初始化一个空栈,
k初始化为极小值(代码中使用 $-0x3f3f3f3f$,约等于 $-1.06 \times 10^9$,小于题目给定的数值下界 $-10^9$,确保第一次比较不会被误触发); - 从右向左遍历
nums:- 若
nums[i] < k,直接返回true(找到了 1); - 否则,将栈中所有小于
nums[i]的元素依次弹出,并用k记录弹出元素中的最大值(这些被弹出的元素及其"垫背"关系正是合法的 32 结构); - 将
nums[i]入栈;
- 若
- 若遍历结束仍未返回,说明不存在 132 模式,返回
false。
4.3 过程演示:以[3, 1, 4, 2]为例
已知该样例的答案是[1, 4, 2],即 $i$ 取1、$j$ 取4、$k$ 取2。我们从后往前模拟:
- 枚举到 2:栈内元素为
[2],k = INF(初始极小值),不满足nums[i] < k; - 枚举到 4:
4 > 2,不满足单调递减,弹出2并更新k = 2,随后4入栈。此时栈内为[4],k = 2。注意:4入栈后,栈仍保持单调递减(4在栈底,2已出栈); - 枚举到 1:满足
nums[i] = 1 < k = 2,返回true。
为什么第 3 步的判定是成立的?因为:
1 < 2满足了i与k之间的大小关系(132 中的1 < 2);- 而
k = 2之所以有值,是因为它曾作为4的"牺牲品"被弹出——这意味着4 > 2,即j > k(132 中的3 > 2)确实存在,且4的位置在1与2之间(因为4比1更靠左、比2更靠右才可能在逆序扫描中形成这一弹出关系)。
于是 $(1, 4, 2)$ 三个位置恰好构成完整的 132 模式。
4.4 算法的本质
题解对这一做法的本质做了精确概括:
我们通过维护「单调递减」来确保已经找到了有效的
(j,k)。换句话说如果k有值的话,那么必然是因为有j > k,导致的有值。也就是 132 结构中,我们找到了 32,剩下的i(也就是 132 结构中的 1)则是通过遍历过程中与k的比较来找到。
一句话总结:单调栈负责在 $O(n)$ 内"发现并记录"所有可能的 32 组合中的最优k,剩余的一维枚举交给单次逆序扫描完成,两相结合把原本的三重枚举压成了单趟遍历。
五、正确性证明
从过程上看算法自洽,题解进一步给出了严谨的证明。
设数组中真实存在满足 132 结构的三元组,我们取所有合法组合中k最大的那一组记为ijk。由于算法对i的遍历是从后往前的,i必然会被遍历到;因此若算法漏掉了这组ijk,只可能是:在遍历到i的时刻,变量k没有被更新到真实组合中的k值。此时分两种情况讨论:
变量
k小于真实值:说明真正的k此时还在栈中未被弹出,而遍历位置已经到达了i,这意味着j与k同时存在于栈中。但栈是单调递减的——栈内元素自底向上严格递减,k < j时二者不可能同栈,与单调性矛盾。变量
k大于真实值:说明在真正的k出栈之后,又有比k更大的数值出栈并刷新了变量(同时必然有比该变量更大的值仍在栈中垫底)。但这要么与"ijk是所有合法组合中k最大的组合"这一假设冲突(存在更大的k值组合),要么与"当前遍历位置是i"的事实冲突(说明有更大的元素出现在i右侧更远处,结构上不再符合 $i<j<k$ 的相对顺序)。
综合两种情况:
由于「单调递减」的性质,我们至少能找到「遍历过程中」所有符合条件的
ijk中k最大的那个组合。
一旦找到该组合,遍历到它的i时nums[i] < k判定必然成立,算法必返回true;若全数组不存在 132 模式,则全程无元素能刷新k且所有nums[i] \ge k,最终返回false。正确性得证。
六、代码实现(四种语言)
以下代码直接取自 456. 132 模式(中等) 题解原文,可在 LeetCode 456 题直接提交运行。
Java
class Solution { public boolean find132pattern(int[] nums) { Deque<Integer> d = new ArrayDeque<>(); int n = nums.length, k = -0x3f3f3f3f; for (int i = n - 1; i >= 0; i--) { if (nums[i] < k) return true; while (!d.isEmpty() && d.peekLast() < nums[i]) { // 事实上,k 的变化也具有单调性,直接使用 k = pollLast() 也是可以的 k = Math.max(k, d.pollLast()); } d.addLast(nums[i]); } return false; } }C++
class Solution { public: bool find132pattern(vector<int>& nums) { stack<int> st; int n = nums.size(), k = -0x3f3f3f3f; for(int i = n - 1; i >= 0; i--){ if(nums[i] < k) return true; while(!st.empty() and st.top() < nums[i]) { k = max(k, st.top()); st.pop(); } st.push(nums[i]); } return false; } };Python3
class Solution: def find132pattern(self, nums: List[int]) -> bool: stack = [] n, k = len(nums), -0x3f3f3f3f for i in range(n - 1, -1, -1): if nums[i] < k: return True while stack and stack[-1] < nums[i]: k = max(k, stack.pop()) stack.append(nums[i]) return FalseTypeScript
function find132pattern(nums: number[]): boolean { const d = []; let n = nums.length, k = -0x3f3f3f3f; for (let i = n - 1; i >= 0; i--) { if (nums[i] < k) return true; while (d.length > 0 && d[d.length - 1] < nums[i]) { k = Math.max(k, d.pop()!); } d.push(nums[i]); } return false; };实现要点说明
k的初始值:$-0x3f3f3f3f$(约 $-1.06 \times 10^9$),严格小于题目下界 $-10^9$,保证初始状态下nums[i] < k永不成立,不会误判;- 栈的单调性:每次弹出所有比
nums[i]小的栈顶元素后入栈,保证栈自底向上严格递减; k的更新:k取所有出栈元素的最大值。题解注释特别指出,k的变化本身也具有单调性(每次出栈元素必然大于之前记录的k),因此直接写k = pollLast()同样正确;使用Math.max只是形式上更严谨、更易读;- 提前返回:一旦发现
nums[i] < k,立即返回true,无需继续扫描。
复杂度分析
- 时间复杂度:$O(n)$。每个元素最多入栈一次、出栈一次,均摊 $O(1)$;
- 空间复杂度:$O(n)$。最坏情况下(如数组单调递减)栈中需要容纳全部 $n$ 个元素。
该复杂度优于进阶要求中的 $O(n \log n)$ 树状数组方案,也远优于 $O(n^2)$ 的枚举优化方案。
七、边界情况与易错点
- 数组含负数:
k的初始值必须小于全部元素(含负数),$-0x3f3f3f3f$ 即为本题下界的合适选择;若误用Integer.MIN_VALUE或-1之类的值,可能在负数场景下漏判; - 长度不足 3:$n < 3$ 时不可能存在三元组,算法自然返回
false,无需特判; - 严格不等号:132 模式要求严格小于/大于,因此代码中统一使用
</>比较,等于的情况(如nums[i] == k)不能触发命中; k是"右侧"信息:务必注意逆序遍历的方向性——k记录的一定是当前i右侧(已扫描过的区域)被弹出的最大值,这保证了 $i < j < k$ 的下标顺序约束。
八、同类问题与延伸阅读
本题是"单调栈"思想的经典应用场景之一,仓库的 单调栈专题 汇总了完整的同类题目清单,推荐配合研读:
- 接雨水(困难):单调栈经典应用,利用栈维护柱子的高度关系;
- 下一个更大元素 I(简单):逆序遍历 + 单调栈找右侧最近更大元素,是理解 132 模式算法的基础模板;
- 柱状图中最大的矩形(困难):单调栈 + 边界扩展的进阶应用;
- 每日温度(中等):单调栈的入门级实操题。
研读建议:先通过 496 题掌握"逆序遍历 + 单调栈维护右侧信息"的基本盘,再回到本题体会"单调栈 + 出栈元素最值"如何把两维约束压缩成一维比较——这种"用栈维护局部结构、用标量记录全局最优候选"的组合技巧,在区间类、子序列类问题中具有很高的复用价值。
本系列完整题解按题目编号组织于仓库LeetCode/目录下(如本文对应 LeetCode/451-460/456. 132 模式(中等).md),按算法分类的索引见Index/目录,可继续翻阅对应专题深化学习。
- 教程
- 文档
【免费下载链接】LogicStack-LeetCode
公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码
相关推荐
LeetCode 456 题解:132 模式判定与单调栈 O(n) 解法全解析
LeetCode 456 题解:132 模式判定与单调栈 O n 解法全解析 本篇基于 leetcode https://link.gitcode.com/i/
文档教程知识库LeetCode 456「132 模式」题解:AlgoNote 单调栈判定子序列模式
LeetCode 456「132 模式」题解:AlgoNote 单调栈判定子序列模式 导读 本文以 AlgoNote 仓库中 0456. 132 模式 题解 h
教程文档知识库LogicStack-LeetCode 刷穿系列:503. 下一个更大元素 II——循环数组下的单调栈 O(n) 通解
LogicStack LeetCode 刷穿系列:503. 下一个更大元素 II——循环数组下的单调栈 O n 通解 导读 本篇基于「宫水三叶的刷题日记」系列仓
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考