第3章|栈与队列
Unit 07 · 从普通栈进入单调栈
当前更大的元素,
可以一次解决多个历史问题。
普通栈只规定后进先出;单调栈还主动删除已经得到答案的元素,只保留“未来仍在等待更大值”的候选。理解弹栈时机,比记住模板更重要。
题目要求
给定两个下标从 0 开始且元素互不相同的数组 nums1 与 nums2,其中 nums1 是 nums2 的子集。对 nums1 中每个值,寻找它在 nums2 中右侧第一个严格更大的元素;不存在时返回 -1。
约束与目标
1 ≤ len(nums1) ≤ len(nums2) ≤ 10000 ≤ nums1[i], nums2[i] ≤ 10⁴- 两个数组内元素均互不相同,且
nums1中的值都出现在nums2中。 - 进阶目标:时间复杂度
O(len(nums1)+len(nums2))。
示例 1
示例 2
复杂度要求
暴力查询最坏为 O(mn);主方法用单调栈预处理 nums2,再通过哈希表回答 nums1,总时间 O(m+n),空间 O(n)。
不是右侧最大值,而是从当前位置向右遇到的第一个严格更大值。
已经扫描,但右侧第一个更大值仍未出现的历史元素。
栈底到栈顶保持严格递减;新值更大时连续弹出更小栈顶。
元素被弹出时,当前值就是它右侧第一个更大的元素,因此答案在此刻确定。
通过轮转队列,让队首始终承担“栈顶”的接口语义。关注的是元素应该按什么顺序离开。
栈不再模拟接口,而是保存尚未找到答案的历史元素。当前值到来时,可以一次解决多个待决问题。
Pending Stack 栈底 → 栈顶严格递减
next_greater 映射 元素被弹出时写入
在当前画面中,nums2 里 1 的下一个更大元素是什么?
迁移题:LeetCode #503「下一个更大元素 II」
这次不是只给循环扫描模板,而是先读懂官方原题,再完成从“值映射”到“按索引回答”、从线性右侧到循环右侧的完整迁移。
题目要求
给定一个循环整数数组 nums。对数组中的每个位置,返回沿遍历顺序向后遇到的第一个严格更大的数。数组末尾之后会回到数组开头;若完整绕行后仍不存在更大值,则该位置答案为 -1。
官方约束
1 ≤ nums.length ≤ 10⁴-10⁹ ≤ nums[i] ≤ 10⁹- 数组可以包含重复值。
- 输出长度与
nums相同,答案按原下标对应。
官方示例 1
官方示例 2
注意:题目寻找的是遍历顺序中的第一个严格更大值,不是循环范围内的最大值。
最后一个位置之后继续访问 index 0,但单个位置最多只需要考察其余 n−1 个位置。
重复值可能位于不同位置并拥有不同搜索起点,因此答案必须按索引保存。
相等不能解决栈顶。比较条件仍然是当前值严格大于待决位置的值。
完整循环后仍留在栈中的位置没有更大元素,初始化的 −1 会自然保留。
读题检查 1:函数返回什么?
读题检查 2:为什么 [1,2,1] 的最后一个 1 答案是 2?
#496 下一个更大元素 I
#503 下一个更大元素 II
在 [1,2,1] 中,两个值为 1 的位置拥有不同的线性右侧,但都需要独立记录答案。用值作为字典键会混淆重复元素;保存索引既能区分位置,也能通过 nums[index] 完成比较。
迁移检查:#503 的 pending stack 最准确的语义是什么?
槽位 A|答案与待决状态
每个原数组位置都要有独立答案,默认不存在。
槽位 B|模拟循环数组
不复制业务逻辑,用取模把 2n 个逻辑位置映射回原数组。
槽位 C|弹栈时写入答案
当前值第一次严格超过某个待决位置时,该位置答案确定。
槽位 D|第二轮只补答案
原位置只能进入 pending stack 一次,避免制造重复任务。
from typing import List
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
n = len(nums)
answer = [-1] * n
stack = []
for step in range(2 * n):
index = step % n
while stack and nums[index] > nums[stack[-1]]:
answer[stack.pop()] = nums[index]
if step < n:
stack.append(index)
return answer展开查看 #496 → #503 的最小代码差异
#496
next_greater = {}
stack 保存 value
for value in nums2:
while value > stack[-1]:
next_greater[pop()] = value
return 按 nums1 查询字典#503
answer = [-1] * n
stack 保存 index
for step in range(2*n):
index = step % n
while nums[index] > nums[stack[-1]]:
answer[pop()] = nums[index]
只在第一轮 push index循环扫描画布
准备开始当前算法状态
第二轮只是给第一轮遗留的索引提供“绕回开头”的候选。如果再次压入,会为同一个原位置制造重复待决任务。
可选挑战:不看上方代码,独立重写
检查器只验证关键语义,不会把完整答案覆盖进编辑器。
迁移完成:单调栈思想没有变,坐标系变了
#496 用值映射回答一个查询子集;#503 允许重复值且要求每个位置的答案,因此栈必须保存索引。循环性由 2n 次逻辑扫描和 step % n 提供,第二轮只补答案,不重复创建待决位置。
Unit 07 Skill 卡
看到“右侧第一个更大/更小元素”时,尝试把尚未找到答案的历史位置放入单调栈。当前元素到来时,连续弹出能够被它解决的栈顶;循环数组则用取模延伸搜索范围,并坚持每个原位置只入栈一次。