:单调栈 O(n) 解法全解析|LogicStack-LeetCode 刷穿系列)
教程文档【免费下载链接】LogicStack-LeetCode公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码项目地址https://gitcode.com/gh_mirrors/lo/LogicStack-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$即 $i1,\ j2,\ k3$ 对应 $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)$ 做法。$n10^4$ 时 $O(n^2)$ 意味着 $10^8$ 次运算逼近多数判题环境的超时临界线因此 $O(n^2)$ 同样不够稳妥。题解原文还提到一条替代路线树状数组。利用树状数组维护值域上的前缀最值/计数配合值域离散化可以做到 $O(n \log n)$能够通过本题。但树状数组方案代码量较大且需要额外理解离散化等前置知识并非本题的最优解。真正的亮点在于下面的单调栈 $O(n)$ 做法——它比树状数组更快代码也更简洁。三、从 132 的结构特性出发三种枚举视角在确定一个数之后如何快速找到另外两个数题解从 132 结构中每个位置的角色出发给出了三种分析视角枚举i1 的角色i是 132 结构中最小的数需要从i后面找到一对(j, k)使得两者都大于i且 $j k$。由于遍历是单向的可以转化为找k要求k i同时保证在[i, k]之间存在比k更大的数。枚举j3 的角色j是 132 结构里最大的数需要在j的右边找比j小的「最大」的数在j的左边找比j小的「最小」的数。这很容易联想到单调栈但朴素单调栈只能帮我们找到左边或右边「最近」的数无法直接满足「最大」和「最小」的要求需要引入额外逻辑。枚举k2 的角色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枚举到 44 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 k132 中的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右侧更远处结构上不再符合 $ijk$ 的相对顺序。综合两种情况由于「单调递减」的性质我们至少能找到「遍历过程中」所有符合条件的ijk中k最大的那个组合。一旦找到该组合遍历到它的i时nums[i] k判定必然成立算法必返回true若全数组不存在 132 模式则全程无元素能刷新k且所有nums[i] \ge k最终返回false。正确性得证。六、代码实现四种语言以下代码直接取自 456. 132 模式中等 题解原文可在 LeetCode 456 题直接提交运行。Javaclass Solution { public boolean find132pattern(int[] nums) { DequeInteger 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; } }Cclass Solution { public: bool find132pattern(vectorint nums) { stackint 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; } };Python3class 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 FalseTypeScriptfunction 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 系列文章源码项目地址https://gitcode.com/gh_mirrors/lo/LogicStack-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 通解 导读 本篇基于「宫水三叶的刷题日记」系列仓教程文档上一篇如何快速抢到心仪演唱会门票3个自动抢票工具的终极指南下一篇Humanizer 集合本地化格式化ICollectionFormatter 接口与多语言列表拼接实现解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考