发布时间:2026/9/7 22:41:15
单调栈算法:原理、实现与工程应用详解 1. 单调栈算法工程师的必备武器第一次听说单调栈是在准备Google面试的时候当时刷到Leetcode 496这道下一个更大元素的题目暴力解法O(n²)的时间复杂度让我抓耳挠腮。直到看到讨论区有人用单调栈在O(n)时间内解决那种醍醐灌顶的感觉至今难忘。单调栈就像是一把瑞士军刀看似简单却能在各种看似复杂的问题中游刃有余。单调栈的核心在于维护一个栈内元素单调递增或单调递减的特性。这种数据结构特别适合处理下一个更大/更小元素、柱状图中最大矩形这类需要比较相邻元素的问题。在实际工程中我曾在处理电商价格波动分析时就用到了单调栈的思路快速找出了价格异常波动的关键时间点。2. 单调栈的核心原理与实现2.1 单调栈的两种基本形态单调栈分为单调递增栈和单调递减栈两种基本类型单调递增栈栈底到栈顶元素保持递增单调递减栈栈底到栈顶元素保持递减以Leetcode 739每日温度为例我们需要找到每一天之后更高温度出现的天数差。这个问题完美契合单调递减栈的特性def dailyTemperatures(T): stack [] res [0] * len(T) for i in range(len(T)): while stack and T[i] T[stack[-1]]: prev stack.pop() res[prev] i - prev stack.append(i) return res这段代码中我们维护一个存储下标的栈保证栈内对应温度是递减的。当遇到更高温度时就计算天数差并更新结果。2.2 时间复杂度分析单调栈最精妙的地方在于它的时间复杂度。虽然看起来有嵌套循环但每个元素最多入栈和出栈各一次所以整体时间复杂度是O(n)。这比暴力解法的O(n²)有了质的飞跃。提示在面试中能够清晰解释为什么是O(n)而不是O(n²)会大大加分。可以类比为摊还分析的概念每个元素只被处理常数次。3. 单调栈的经典应用场景3.1 下一个更大元素系列Leetcode上有多个下一个更大元素的变种题下一个更大元素 I下一个更大元素 II (循环数组)下一个更大元素 III以503题为例处理循环数组的技巧是在原数组后再拼接一次数组或者用取模运算来模拟循环def nextGreaterElements(nums): n len(nums) res [-1] * n stack [] for i in range(2 * n): while stack and nums[i % n] nums[stack[-1]]: res[stack.pop()] nums[i % n] if i n: stack.append(i) return res3.2 柱状图最大矩形问题Leetcode 84柱状图中最大的矩形是单调栈的另一个经典应用。这道题需要同时考虑左右边界def largestRectangleArea(heights): heights [0] heights [0] stack [] res 0 for i in range(len(heights)): while stack and heights[i] heights[stack[-1]]: h heights[stack.pop()] w i - stack[-1] - 1 res max(res, h * w) stack.append(i) return res这里我们在数组前后各加一个0作为哨兵简化边界条件的处理。这种技巧在很多单调栈问题中都很有用。4. 单调栈的高级应用与变形4.1 接雨水问题Leetcode 42接雨水是单调栈的一个有趣变形。我们需要计算柱子之间能接多少雨水def trap(height): stack [] res 0 for i in range(len(height)): while stack and height[i] height[stack[-1]]: bottom stack.pop() if not stack: break left stack[-1] h min(height[left], height[i]) - height[bottom] w i - left - 1 res h * w stack.append(i) return res这个解法中我们维护一个单调递减栈。当遇到更高的柱子时计算前一个柱子能接的雨水量。4.2 最大矩形问题Leetcode 85最大矩形可以看作是柱状图问题的二维扩展。我们可以将每一行转化为柱状图高度然后复用84题的解法def maximalRectangle(matrix): if not matrix: return 0 m, n len(matrix), len(matrix[0]) heights [0] * n res 0 for i in range(m): for j in range(n): heights[j] heights[j] 1 if matrix[i][j] 1 else 0 res max(res, largestRectangleArea(heights)) return res这种将二维问题降维到一维的思路非常实用也是面试中的高频考点。5. 单调栈的常见陷阱与调试技巧5.1 边界条件处理单调栈最容易出错的就是边界条件的处理。比如在84题中如果不加哨兵就需要额外处理栈为空的情况# 不加哨兵的版本 while stack and heights[i] heights[stack[-1]]: h heights[stack.pop()] # 需要额外判断栈是否为空 w i - stack[-1] - 1 if stack else i res max(res, h * w)5.2 元素相等时的处理当遇到相等元素时是弹出还是保留这取决于具体问题。在大多数情况下我们可以选择弹出或保留都可以但有些问题需要特别注意# 在每日温度问题中遇到相同温度可以保留 while stack and T[i] T[stack[-1]]: # 只有大于才弹出 # 在接雨水问题中遇到相等高度应该弹出 while stack and height[i] height[stack[-1]]: # 大于等于就弹出5.3 调试技巧当单调栈代码出现问题时可以打印中间状态来调试def dailyTemperatures(T): stack [] res [0] * len(T) for i in range(len(T)): print(fi{i}, T[i]{T[i]}, stack{stack}) while stack and T[i] T[stack[-1]]: prev stack.pop() res[prev] i - prev print(f update res[{prev}]{res[prev]}) stack.append(i) return res6. 单调栈的工程应用实例6.1 股票价格分析在实际工程中我曾用单调栈分析股票价格的支撑位和阻力位。通过维护一个单调递减栈可以快速找出价格下跌时的关键支撑位def find_support_levels(prices): stack [] supports [] for i in range(len(prices)): while stack and prices[i] prices[stack[-1]]: stack.pop() if stack: supports.append(prices[stack[-1]]) else: supports.append(None) stack.append(i) return supports6.2 日志时间窗口分析另一个应用场景是分析服务器日志中的异常峰值。使用单调栈可以高效地找出请求量突增的时间点def find_traffic_spikes(requests, window60): spikes [] stack [] for i in range(len(requests)): while stack and requests[i] 1.5 * requests[stack[-1]]: spike_time stack.pop() spikes.append((spike_time, i - spike_time)) while stack and i - stack[0] window: stack.pop(0) stack.append(i) return spikes7. 单调栈的扩展学习资源7.1 Leetcode单调栈题目清单建议按以下顺序刷题下一个更大元素 I (简单)每日温度 (中等)下一个更大元素 II (中等)柱状图中最大的矩形 (困难)接雨水 (困难)最大矩形 (困难)股票价格跨度 (中等)7.2 可视化学习工具推荐使用VisuAlgo等算法可视化工具观察单调栈的运行过程。动态演示能帮助理解元素入栈和出栈的时机。7.3 复杂度证明的数学基础想深入理解为什么单调栈是O(n)的同学可以学习摊还分析(Amortized Analysis)的概念。这在《算法导论》第17章有详细讲解。8. 面试中的单调栈问题8.1 常见考察形式面试官可能会直接出经典单调栈题目给出实际问题让你抽象出单调栈模型要求优化一个暴力解法到O(n)8.2 解题思路模板遇到新问题时可以这样思考问题是否涉及比较相邻元素是否需要维护某种单调性能否将问题转化为下一个更大/更小元素问题8.3 面试回答技巧解释思路时可以这样表述 这个问题需要找到每个元素右边第一个比它大的元素这让我想到可以用单调栈来维护一个递减序列。当遇到比栈顶大的元素时我们就找到了栈顶元素的下一个更大元素...9. 单调栈与其他数据结构的结合9.1 单调栈前缀和Leetcode 1124表现良好的最长时间段就是单调栈和前缀和的结合def longestWPI(hours): prefix [0] for h in hours: prefix.append(prefix[-1] (1 if h 8 else -1)) stack [] for i in range(len(prefix)): if not stack or prefix[i] prefix[stack[-1]]: stack.append(i) res 0 for j in range(len(prefix)-1, -1, -1): while stack and prefix[j] prefix[stack[-1]]: res max(res, j - stack.pop()) return res9.2 单调栈动态规划有些问题需要结合动态规划的思想如Leetcode 975奇偶跳def oddEvenJumps(A): n len(A) next_higher [0] * n next_lower [0] * n # 使用单调栈预处理next_higher和next_lower # ...省略实现细节... higher [False] * n lower [False] * n higher[-1] lower[-1] True res 1 for i in range(n-2, -1, -1): higher[i] lower[next_higher[i]] if next_higher[i] ! -1 else False lower[i] higher[next_lower[i]] if next_lower[i] ! -1 else False res higher[i] return res10. 从单调栈到单调队列掌握了单调栈后可以进一步学习单调队列。它们的思想一脉相承只是操作从一端扩展到了两端。Leetcode 239滑动窗口最大值就是单调队列的经典应用def maxSlidingWindow(nums, k): from collections import deque q deque() res [] for i in range(len(nums)): while q and nums[i] nums[q[-1]]: q.pop() q.append(i) if q[0] i - k: q.popleft() if i k - 1: res.append(nums[q[0]]) return res这种维护窗口内单调性的思想在解决各种滑动窗口问题时非常高效。

相关新闻

2026/9/7 22:41:15

封测厂数字化的拐点:今年为什么都在抢着上 MES

如果你最近去华东、华南的封测厂转一圈,会发现一个很明显的信号:原本靠 Excel、白板和老师傅经验管产线的厂子,今年突然都在问"封测MES怎么上"。这股劲头不是跟风,是几个硬约束同时到了临界点。第一个临界点&#xff1a…

2026/9/7 22:41:15

第9.2节 Python的文件打开函数open详解

一、 引言在着手处理某一个文件之前,多数情形下都得先将文件予以打开, 如此方能开展后续操作, 而开展操作所处即借助内置函数open去开启这样一个文件。open函数着实属于一种内置函数, 经由io模块定义而来的函数open乃是此内置函数的同义词(这乃是官网里针…

2026/9/7 22:41:15

MyPy 安装与配置快速教程:新手如何第一次跑通静态类型检查

MyPy 安装与配置快速教程:新手如何第一次跑通静态类型检查 【免费下载链接】mypy Optional static typing for Python 项目地址: https://gitcode.com/GitHub_Trending/my/mypy 写 Python 时最坑的类型 bug,往往要等程序跑起来才暴露。MyPy 在代码…

2026/9/7 23:51:48

Flutter设备守护进程启动失败解决方案

1. 问题背景与现象描述 最近在配置Flutter开发环境时,遇到了一个棘手的问题:Flutter Device Daemon启动失败。这个问题导致Android Studio无法识别连接的设备,严重影响了开发效率。具体表现为运行 flutter doctor 命令时,控制台…

2026/9/7 23:51:48

从望文生义到构词逻辑:中英文思维差异如何重塑认知

开头中文的“望文生义”能力,我用一个例子就能让你瞬间体验:“打电话”。中文使用者看到这三个字,脑海里立刻浮现出“拨号—接通—说话”的完整行为链条,甚至不需要刻意理解。但把它翻译成英文“call somebody”,拆开来…

2026/9/7 23:51:48

Electron 应用分发实战:打包、asar 归档与重新品牌化指南

Electron 应用分发实战:打包、asar 归档与重新品牌化指南 【免费下载链接】electron :electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS 项目地址: https://gitcode.com/GitHub_Trending/el/electron 本篇技术指南围绕 Electron…

2026/9/7 23:51:48

从Excel记账到Python数据分析:家庭支出自动化统计实战指南

1. 从Excel记账到Python分析:我为什么迈出这一步1.1 记账四年,Excel总表越来越难伺候我家记账记了快四年,一直用的Excel。一开始确实够用——每月底花十几分钟把微信、支付宝的账单手工录入一张总表,再用SUMIF、SUMIFS这些函数按分…

2026/9/7 23:51:48

从TCP/IP协议族到Socket编程:高频报错排查与实战指南

先说一个结论:标准网络协议栈里并没有“TCPTP”这个协议。这个写法大概率是 TCP/IP 的误写,也有人会把“TCP 和 UDP”连在一起顺手打个 TCPTP 出来。这些年我带过不少刚入行的同事,新人在看网络编程资料时最常出现的字面混淆就是这个词。所以…

2026/9/7 23:46:47

用Python驱动COPASI:插件体系与批量参数扫描实战

1. 任务插件生态:COPASI 的功能单元不止是“按钮”COPASI 这类生化系统仿真软件,绝大多数用户的使用路径是:打开 GUI、加载或建一个模型、点 Time-Course 或 Steady-State、看结果、导图。这套流程在单次实验里够用,但当你面对“同…

2026/9/7 0:47:43

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/7 0:14:19

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/7 0:14:17

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/7 0:03:36

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

这次我们来看一个把目标检测算法和桌面端工具结合得很典型的项目:基于 YOLOv8 PyQt5 的麦穗稻穗检测识别系统。这个项目本身不是新概念,但它的价值在于落地形态很完整。YOLOv8 负责核心的麦穗稻穗目标检测,PyQt5 负责提供可视化的桌面交互界…

2026/9/7 0:03:36

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

简介:UL 1642是锂电池安全领域的重要规范,本中文版资源适合锂电池制造商、检测机构工程师及产品认证相关人员阅读,用于理解电池在设计与制造层面的安全要求、测试方法与合规要点。资源共1个PDF文件,压缩包大小834KB,便…

2026/9/7 0:03:36

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

简介:BS EN 13814-1:2019是英国采纳欧洲标准EN 13814-1:2019的正式版本,由BSI标准出版,重点规定游乐设施和游乐设备在设计与制造环节的安全准则,与BS EN 13814-2:2019、BS EN 13814-3:2019共同取代旧版BS EN 13814:2004。该标准面…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/7 22:45:59

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…