Next Greater Element II
Monotonic stack with double pass
Circular array — iterate twice through the array.
Problem
Given a circular integer array nums, return an array answer such that answer[i] is the next greater element of nums[i] — the first value strictly greater than nums[i] that you encounter walking forward, wrapping around the end if needed.
If no such value exists, answer[i] = -1.
Intuition
The non-circular version (#496) solves this with a monotonic stack: walk the array, pop everything smaller than the current value, record the current value as their answer.
The circular twist is that after the last index we keep looking at the first index, the second, and so on — until we either find something larger or have done a full extra lap.
The trick: simulate the circular walk by iterating 2 * n times and indexing with i % n. The first n iterations seed the stack with unresolved indices; the second n iterations get one more shot at resolving them — but we don't push anything new during the second lap, since pushing past n cannot change any answer.
Approach
Brute Force
O(n²)O(1)For each i, walk forward up to n - 1 steps using (i + k) % n and return the first index with a strictly larger value. If we exhaust all n - 1 neighbors, the answer is -1.
Direct, easy to defend in an interview as a baseline, and obviously quadratic.
impl Solution {
pub fn next_greater_elements(nums: Vec<i32>) -> Vec<i32> {
let n = nums.len();
let mut answer = vec![-1; n];
for i in 0..n {
for k in 1..n {
let j = (i + k) % n;
if nums[j] > nums[i] {
answer[i] = nums[j];
break;
}
}
}
answer
}
}
function nextGreaterElements(nums: number[]): number[] {
const n = nums.length;
const answer = new Array<number>(n).fill(-1);
for (let i = 0; i < n; i++) {
for (let k= 1; k < n; k++) {
const j= (i + k) % n;
if (nums[j] > nums[i]) {
answer[i] = nums[j];
break;
}
}
}
return answer;
}
Monotonic Stack with 2n Pass
O(n)O(n)Walk indices 0..2n, and at each step look at nums[i % n].
- The stack stores indices of values whose next greater is still unresolved.
- When the current value is strictly larger than the value at the stack's top, pop and record the current value as that index's answer.
- On the second lap (
i >= n) the stack contains only values that survived a full pass — meaning everything to their right (including the wrap-around prefix that we are now revisiting) was smaller or equal. We do not push during the second lap; pushing later wraps would only enqueue indices that can never get a chance to resolve.
Each index pops at most once, so total work is O(n) even though the index counter runs to 2n.
impl Solution {
pub fn next_greater_elements(nums: Vec<i32>) -> Vec<i32> {
let n = nums.len();
let mut answer = vec![-1; n];
let mut stack: Vec<usize> = Vec::with_capacity(n);
for i in 0..(2 * n) {
let x = nums[i % n];
while let Some(&top) = stack.last() {
if nums[top] < x {
stack.pop();
answer[top]= x;
} else {
break;
}
}
if i < n {
stack.push(i);
}
}
answer
}
}function nextGreaterElements(nums: number[]): number[] {
const n = nums.length;
const answer = new Array<number>(n).fill(-1);
const stack: number[] = [];
for (let i = 0; i < 2 * n; i++) {
const x= nums[i % n];
while (stack.length > 0 && nums[stack[stack.length - 1]] < x) {
answer[stack.pop()!]= x;
}
if (i < n) {
stack.push(i);
}
}
return answer;
}A few notes:
- Strict
<(not<=) — equal values don't count as "next greater". - Indices, not values — the stack stores positions so we can write directly into
answer. - The second lap is read-only — anything left on the stack after
i = 2n - 1keeps its-1.
Trace
The 2n pass on nums = [1, 2, 1]:
| i | i%n | x | stack before | pops | stack after | answer updates |
|---|---|---|---|---|---|---|
| 0 | 0 | 1 | [] | — | [0] | — |
| 1 | 1 | 2 | [0] | pop 0 | [1] | answer[0] = 2 |
| 2 | 2 | 1 | [1] | — | [1, 2] | — |
| 3 | 0 | 1 | [1, 2] | — | [1, 2] | — |
| 4 | 1 | 2 | [1, 2] | pop 2 | [1] | answer[2] = 2 |
| 5 | 2 | 1 | [1] | — | [1] | — |
Index 1 (value 2) never pops — it's the global max — so answer[1] stays -1. Final answer [2, -1, 2].
Edge cases
- All-equal array — nothing pops; every answer stays
-1. Strict comparison is what protects this case. - Strictly ascending
[1, 2, 3, 4]— each element pops the previous on the first lap; the trailing4never pops; answer is[2, 3, 4, -1]. - Strictly descending
[5, 4, 3, 2, 1]— first lap pushes all; second lap pops every index except the first (the global max) by visiting5again. Answer:[-1, 5, 5, 5, 5]. - Single element — the only
-1answer.
The doubled-iteration trick generalizes: any "next greater / smaller in a circular structure" reduces to 2 * n indices through i % n plus a guard against re-pushing.
Takeaway
Circular variants of stack/queue problems usually become linear once you double the iteration range and suppress writes on the second lap. The data structure does not need to know the array is circular — only the index supplier does.
This is the same shape as a ring buffer scan in production systems: walk twice, mutate once.
More in Monotonic Stack & Deque
The basic pattern. Process right to left, stack holds candidates.
Monotonic deque. Maintain decreasing order; front is always the max.
THE canonical mono stack problem. Must solve in under 15 minutes.
Row-by-row histogram heights, then apply #84 per row.