下一个更大元素 II:绕一圈,递减栈出答案
环上的「下一个」可以越过末尾回到下标 0。把数组看成接了两遍,从右往左弹掉不够大的栈顶。
把环摊成虚拟长度 2n,从 i=2n−1 扫到 0,用 i%n 取真实值。维护值递减栈:弹出所有 ≤ 当前值的栈顶;仅当 i<n 时,栈顶就是 nums[i] 右边第一个更大的数,没有则保持 −1。当前值入栈。时间 O(n),空间 O(n)。
这是 LeetCode 503. Next Greater Element II。给你一个循环数组——最后一个元素的下一个是第一个——对每个位置找出它右边第一个严格更大的值;绕一圈都没有则填 −1。
主例 nums = [1, 2, 1],答案 [2, −1, 2]。下标 0 的 1 右边立刻是 2;下标 1 的 2 往右只有 1,再绕回左边仍是 1,没有比 2 大的,填 −1;下标 2 的 1 右边空了,绕到下标 0 仍是 1,再往右才是 2。
普通「下一个更大」只看右边,扫一遍单调栈就够。环把「右边」延长到了下标 0 附近,缺的是多看一圈的合法范围。本文把数组虚接成两份,用同一只递减栈从右往左结算。
环形:下一个可能绕到前面
非环数组里,下标 i 的候选只在 i+1 .. n−1。主例若不当环,第三个 1 右边是空,会错成 −1,但题目允许它绕到前面的 2。第一直觉是对每个 i 从 i+1 走到 n−1 再从 0 走到 i−1,平方级能做对。
缺的不是「可以绕」,而是怎样把绕一圈变成普通的「只看右边」。把 nums 在想象里再接一份:下标 0..n−1 是第一圈,n..2n−1 是同一段值的第二圈。对真实下标 i,它在虚拟数组里右边的那段,正好覆盖「原右边 + 绕回左边直到自己之前」。不必真的分配 2n 的数组,取值用 i%n。
第二圈只用来给第一圈当「更右边」的候选,答案只写回 i<n 的位置。每个位置最多被作为候选看两次,再多一圈不会出现新的更大元素——绕过自己回到自己,严格更大已经不可能。
虚拟拼接拼接两次不真正构造数组,用 i%n 取下标即可。答案数组仍是长度 n,只有虚拟下标落到第一圈时才写入。
从右往左扫,递减栈出答案
「下一个」在右侧,所以先处理右边,左边查询时答案已经压在栈里。栈里放值,自底到顶递减:栈顶是当前位置右边还没被挡住的、最近的较大候选。遇到更大的值,它左边那些 ≤ 它的候选再也不会成为任何人的「下一个更大」,弹掉。
从 i=2n−1 往左。每一格:先弹出所有 ≤ nums[i%n] 的栈顶;若 i<n 且栈非空,res[i] 写成栈顶;最后把 nums[i%n] 入栈。弹出用 ≤ 而不是 <,因为题目要严格更大,相等也挡不住、也当不成答案。
用手走主例 n=3,虚拟下标 5 到 0,对应演示。i=5,值 1,栈空,不写答案(i≥n),1 入栈。i=4,值 2,栈顶 1≤2,弹出,栈空,2 入栈;演示把虚拟格画成 −1。i=3,值 1,2 比 1 大,不弹,1 入栈,栈是 [2,1]。i=2,值 1,弹出相等的 1,栈顶剩 2,i<n,res[2]=2,再把 1 入栈。演示第三帧钉住这一步。
i=1,值 2,弹出 1 和 2,栈空,res[1] 保持 −1,2 入栈。i=0,值 1,栈顶 2 更大,res[0]=2。答案 [2,−1,2]。2 之所以是 −1,不是没绕圈,而是绕完一圈仍没有严格更大的数。
Go:虚拟拼接 + 单调栈
func nextGreaterElements(nums []int) []int {n := len(nums)res := make([]int, n)for i := range res { res[i] = -1 }stack := []int{}for i := 2*n - 1; i >= 0; i-- {for len(stack) > 0 && stack[len(stack)-1] <= nums[i%n] {stack = stack[:len(stack)-1]}if i < n && len(stack) > 0 {res[i] = stack[len(stack)-1]}stack = append(stack, nums[i%n])}return res}
1答案先垫成 −1。绕完仍没有更大元素的位置,包括主例的 2,会保持这个初值。
2i 从 2n−1 降到 0。取值用 i%n,第二圈和第一圈读到同一段数。
3弹出 ≤ 当前值的栈顶。相等也弹,保证留下来的栈顶严格更大。
4只有 i<n 才写 res。第二圈只负责把候选压进栈,不覆盖答案。
5当前值入栈,供更左边的位置查询。栈里放的是值;若改成压下标,弹出条件写成 nums[栈顶] ≤ 当前值,判断同一件事。
总结
环 = 虚接两遍 + 从右往左递减栈,i<n 才记。主例 [2,-1,2]。
- 不当环会把主例第三个 1 错成 −1;多看一圈后它的答案是 2。
- 弹出条件是 ≤,相等既当不成答案,也挡不住更左边。
- 与 LC496 的差别是下标成环,不是「在另一个数组里查询」。