当前:LC496 · 下一个更大元素 I · 首次出现于 Day 43 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC496 · Next Greater Element I · 单调栈

下一个更大元素 I:单调递减栈一次扫清

答案在弹出时写下。当前数比栈顶大,栈顶右边第一个更大的就是它。

在 nums2 上维护从底到顶递减的栈。扫到 x 时,所有小于 x 的栈顶都被 x 打败:弹出并记 next[栈顶]=x,再把 x 压进去。扫完还留在栈里的数右边没有更大,记 −1。nums1 是 nums2 的子集,按 nums1 的顺序查这张表即可。每个数入栈出栈各一次,时间 O(n+m)。

时间 O(n+m)空间 O(n)结论先行 · 全文约 6 节
导读

这是 LeetCode 496. Next Greater Element I。nums1 是 nums2 的子集,两数组里的数都互不相同。对 nums1 里的每个数 x,在 nums2 里找到 x,再看它右边第一个比它大的数;没有就记 −1。按 nums1 的顺序返回这些答案。

主例 nums1 = [4, 1, 2],nums2 = [1, 3, 4, 2]。4 在 nums2 里右边只有 2,没有更大,−1;1 右边第一个更大是 3;2 已经在最右,−1。答案 [-1, 3, -1]。

对 nums1 的每个查询从 x 的位置往右扫,最坏 O(n·m)。缺的不是「右边有没有更大」,而是 nums2 被同一段右侧反复扫描。本文在 nums2 上走一遍单调栈,弹出时把答案写进哈希,最后只查表。

查询可以等,nums2 不能扫两遍

朴素做法对 nums1 的每个 x 先在 nums2 里定位,再向右找第一个更大。主例三次查询分别走过 [2]、[3,4,2]、[],短,看起来不疼。nums2 一长,同一段右侧会被每个较小的左边元素各扫一次。

观察:一个数的「下一个更大」只取决于它右边,和 nums1 无关。nums1 只决定输出顺序。所以应先在 nums2 上把每个值的答案算完,再按 nums1 取。

还缺一个结构:左边那些「还没被更大的数打败」的人,应该按从近到远排好,让新来的 x 一次性能把所有比自己小的人结算掉。后进的更靠右、更近,先被 x 检查——这又是栈。栈里保持递减,是为了保证栈顶就是「离 x 最近、还在等更大」的那一个。

被弹出的栈顶,答案就是当前数

从左到右扫 nums2,栈里存放还没有下一个更大元素的值,自底到顶递减。遇到 x:当栈不空且栈顶 < x,栈顶的右边第一个更大就是 x,写入哈希并弹出;直到栈空或栈顶 ≥ x,再把 x 压入。x 自己也开始等待。

为什么不会漏、不会错?被弹出的栈顶和 x 之间,没有比栈顶更大的数——否则它早就被中间那个数弹出了。所以 x 确实是它右边第一个更大。比 x 大的旧栈顶继续留着,等更右边。

用手走主例 nums2 = [1, 3, 4, 2]。1 入栈,栈 [1]。3 > 1,弹出 1,记下 1→3,3 入栈,栈 [3]。4 > 3,弹出 3,记下 3→4,4 入栈,栈 [4]。2 < 4,2 入栈,栈 [4, 2]。扫完,4 和 2 右边都没有更大,记 4→−1、2→−1。演示四帧就是这四步;最后一帧按 nums1 = [4, 1, 2] 查表,得到 [-1, 3, -1]。

每个值最多入栈一次、出栈一次,扫 nums2 是 O(n),填 nums1 是 O(m)。题目保证数不重复,才能用「值 → 下一个更大」当哈希键;若有重复,栈里必须存下标。

答案写在弹出时入栈只是报到。真正找到「右边第一个更大」的时刻是被更大的新元素弹出去。扫完还留在栈里的人,右边已经没有人了,只能写 −1。
扫描 [1,3,4,2]:4 弹出 3 和 1
1342
递减栈(自底向上)
1
答案表
1 入栈

Go:单调递减栈

solution.goGo
func nextGreaterElement(nums1, nums2 []int) []int {
next := map[int]int{}
stack := []int{}
for _, x := range nums2 {
for len(stack) > 0 && stack[len(stack)-1] < x {
next[stack[len(stack)-1]] = x
stack = stack[:len(stack)-1]
}
stack = append(stack, x)
}
for _, v := range stack { next[v] = -1 }
res := make([]int, len(nums1))
for i, v := range nums1 { res[i] = next[v] }
return res
}

1next 按值记账。本题数不重复,值可以当键。

2内层循环才是结算:栈顶 < x,栈顶的下一个更大就是 x。主例 3 弹出 1,4 弹出 3。

3x 自己入栈继续等。2 打不过 4,只能压在 4 上面。

4循环结束后栈里剩下的人右边已经走完,一律 −1。

5nums1 只决定输出顺序,不再回头扫 nums2。

总结

递减栈里等更大。主例 3 结算 1,4 结算 3,2 和 4 写 −1,查 nums1 得 [-1,3,-1]。

  • nums1 是查询表,不是扫描对象。下一个更大只在 nums2 上算一次。
  • 答案写在弹出时。还留在栈里的数,右边没有更大。
  • 每个元素入栈出栈各一次。环形版本是 LC503,温度版是 LC739。
同族题目
LC503下一个更大元素 II(环形)LC739每日温度LC84柱状图中最大的矩形