单调栈O(n)找下一个更大元素,因各元素至多入栈出栈一次;栈存索引、维持递减序,遇更大数即弹栈并记录答案;环形数组用两次遍历+取模,栈中索引保持0到n-1。

单调栈的核心逻辑:为什么它能 O(n) 找到下一个更大元素
因为每个元素最多入栈、出栈各一次。不是靠暴力扫后续,而是用栈维护“还没找到答案”的候选者,新元素一来,立刻清算所有比它小的旧元素——它们的答案就是当前这个新元素。
关键点在于:栈里永远保持递减(或非递增)顺序。一旦遇到更大的数,就持续弹栈,给弹出的每个元素记下当前值作为 next_greater。
- 栈中存的是索引,不是值,方便回填结果数组
- 初始化结果数组为
-1,代表“没找到”,避免遗漏未出栈的元素 - 遍历完后栈内剩余索引对应元素,答案就是默认的
-1
Python 实现时容易错的三个地方
写出来像模像样,但跑起来答案对不上,大概率栽在这三处:
- 误把
stack.pop()的返回值当索引用——它返回的是索引,但你得用它去查原数组:nums[stack.pop()]是值,stack.pop()本身才是要填答案的位置 - 循环条件写成
while stack and nums[i] > nums[stack[-1]]没问题,但漏了stack非空判断,导致空栈取stack[-1]报IndexError: list index out of range - 结果数组长度设错:必须是
len(nums),且初始全为-1;有人用[]append,顺序和索引就对不上了
正确片段示例:
立即学习“Python免费学习笔记(深入)”;
res = [-1] * len(nums)
stack = []
for i in range(len(nums)):
while stack and nums[i] > nums[stack[-1]]:
idx = stack.pop()
res[idx] = nums[i]
stack.append(i)处理环形数组:% len(nums) 不是万能解药
题目变体常要求“下一个更大元素 II”,即数组首尾相接。直接在原数组上双倍长度模拟,会多算一次,且空间浪费。
更稳妥的做法是遍历两次索引,但只用一个原始数组:
- 外层循环
i in range(2 * len(nums)),但取值用nums[i % len(nums)] - 栈里仍存原始索引(0 到 n-1),避免越界
- 只在第一次遍历时允许入栈(
if i ),否则只出栈不进栈 - 注意:结果数组只更新一次,所以检查
res[idx] == -1再赋值,防止被第二次覆盖
什么时候不该硬套单调栈
如果需求变成“下一个更大元素的下标差”“前一个更大元素”“大于等于而非严格大于”,逻辑要微调,但栈结构还能用。真正该换思路的情况有:
- 需要动态插入/删除中间元素——单调栈是离线算法,不支持修改
- 查询带范围限制(比如“右侧 5 个数以内”)——此时单调栈失去优势,直接滑动窗口或线段树更合适
- 数据量极小(
单调栈真正的价值边界很清晰:单次遍历、静态数组、全局最值关系、严格线性时间。超出这个范围,先想清楚“栈到底在维护什么秩序”,别为了用而用。








