Stage 4: Advanced Techniques·Monotonic Stack & Deque
Medium#5037 min readMonotonic StackArraysLeetCode

Next Greater Element II

Monotonic stack with double pass

Circular array — iterate twice through the array.

Published Apr 16, 2026

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.

Input
nums = [1, 2, 1]
Output
[2, -1, 2]
Explanation
The first 1 sees 2 to its right. The 2 has nothing larger. The trailing 1 wraps around and finds the leading 2.
Input
nums = [1, 2, 3, 4, 3]
Output
[2, 3, 4, -1, 4]
Explanation
The 4 is the global max, so its answer is -1. The trailing 3 wraps and finds 4.

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

TimeO(n²)
SpaceO(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
    }
}
Rust · runnable

Monotonic Stack with 2n Pass

TimeO(n)
SpaceO(n)
Recommended

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
    }
}
Rust · runnable

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 - 1 keeps its -1.

Trace

The 2n pass on nums = [1, 2, 1]:

ii%nxstack beforepopsstack afteranswer updates
001[][0]
112[0]pop 0[1]answer[0] = 2
221[1][1, 2]
301[1, 2][1, 2]
412[1, 2]pop 2[1]answer[2] = 2
521[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 trailing 4 never 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 visiting 5 again. Answer: [-1, 5, 5, 5, 5].
  • Single element — the only -1 answer.
💡
Tip

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