Next Greater Element II — Circular Array with Monotonic Stack (LeetCode 503)
Next Greater Element II solved with monotonic stack and circular array double-length trick. Python solution with step-by-step trace, complexity analysis, and patterns.
What you'll learn
- ✓How to handle circular arrays with the double-length trick
- ✓Monotonic stack for next greater element on circular arrays
- ✓Why iterating twice simulates the wraparound
- ✓Complete Python solution with step-by-step trace
- ✓Comparison with the non-circular version
Prerequisites
- •Monotonic stack — see Next Greater Element
- •Stack basics — see Stacks Intro
Next Greater Element II (LeetCode 503) extends the classic next-greater-element problem to circular arrays. After the last element, you wrap around to the beginning. The key trick: iterate through the array twice using modular indexing.
The Problem
Given a circular integer array, find the next greater number for every element. The “next” search wraps around.
Input: [1, 2, 1]
Output: [2, -1, 2]
Explanation:
1 → next greater is 2
2 → wraps around: 1, 1 — no greater → -1
1 → wraps around: 1, 2 — next greater is 2
Input: [1, 2, 3, 4, 3]
Output: [2, 3, 4, -1, 4]
1 → 2
2 → 3
3 → 4
4 → wraps: 3, 1, 2, 3 — no greater → -1
3 → wraps: 1, 2, 3, 4 → 4
The Circular Challenge
In the non-circular version, we process each element once and look only to the right. With circular arrays, the search continues past the end and wraps to the beginning.
The insight: iterate through the array twice. On the second pass, earlier elements can serve as “next greater” for elements near the end.
Approach: Monotonic Stack + Double Iteration
def nextGreaterElements(nums):
"""
Next Greater Element II — circular array.
Time: O(n), Space: O(n)
"""
n = len(nums)
result = [-1] * n
stack = [] # stores indices
# Iterate through array twice (2n iterations)
for i in range(2 * n):
idx = i % n
# Pop elements smaller than current
while stack and nums[idx] > nums[stack[-1]]:
result[stack.pop()] = nums[idx]
# Only push during the first pass
if i < n:
stack.append(idx)
return result
Why Iterate Twice?
Consider [5, 4, 3, 2, 1]:
- On the first pass (indices 0-4): no element finds a next greater (everything is decreasing)
- On the second pass (indices 5-9, mapping to 0-4): element at index 4 (value 1) sees value 5 at index 0, element at index 3 (value 2) sees value 5, etc.
By processing 2n elements but only pushing indices in the first n, we simulate the circular lookup without actually duplicating the array.
Step-by-Step Trace
nums = [1, 2, 3, 4, 3]
n = 5, result = [-1, -1, -1, -1, -1]
First pass (i = 0 to 4):
─────────────────────────
i=0, idx=0, nums[0]=1
stack empty → push 0
stack = [0]
i=1, idx=1, nums[1]=2
2 > nums[0]=1 → result[0]=2, pop 0
stack empty → stop
push 1
stack = [1]
i=2, idx=2, nums[2]=3
3 > nums[1]=2 → result[1]=3, pop 1
push 2
stack = [2]
i=3, idx=3, nums[3]=4
4 > nums[2]=3 → result[2]=4, pop 2
push 3
stack = [3]
i=4, idx=4, nums[4]=3
3 < nums[3]=4 → no pop
push 4
stack = [3, 4]
Second pass (i = 5 to 9):
─────────────────────────
i=5, idx=0, nums[0]=1
1 < nums[4]=3 → no pop
(don't push, i >= n)
i=6, idx=1, nums[1]=2
2 < nums[4]=3 → no pop
i=7, idx=2, nums[2]=3
3 == nums[4]=3 → no pop (not strictly greater)
i=8, idx=3, nums[3]=4
4 > nums[4]=3 → result[4]=4, pop 4
4 == nums[3]=4 → no pop
stack = [3]
i=9, idx=4, nums[4]=3
3 < nums[3]=4 → no pop
Final result = [2, 3, 4, -1, 4]
Alternative: Actually Duplicate the Array
A conceptually simpler (but slightly less space-efficient) approach:
def nextGreaterElements_dup(nums):
"""Double the array explicitly."""
n = len(nums)
doubled = nums + nums # [1,2,3,4,3,1,2,3,4,3]
result = [-1] * (2 * n)
stack = []
for i in range(2 * n):
while stack and doubled[i] > doubled[stack[-1]]:
result[stack.pop()] = doubled[i]
stack.append(i)
return result[:n]
This uses O(n) extra space for the doubled array. The modular approach avoids this.
Non-Circular vs Circular Comparison
# Non-circular (LC 496/739): iterate once
def next_greater_linear(nums):
n = len(nums)
result = [-1] * n
stack = []
for i in range(n):
while stack and nums[i] > nums[stack[-1]]:
result[stack.pop()] = nums[i]
stack.append(i)
return result
# Circular (LC 503): iterate twice
def next_greater_circular(nums):
n = len(nums)
result = [-1] * n
stack = []
for i in range(2 * n):
idx = i % n
while stack and nums[idx] > nums[stack[-1]]:
result[stack.pop()] = nums[idx]
if i < n:
stack.append(idx)
return result
The only differences:
- Loop runs
2ntimes instead ofn - Use
i % nfor the actual index - Only push during the first pass (
i < n)
Complexity Analysis
| Metric | Value |
|---|---|
| Time | O(n) — each index pushed once, popped at most once, over 2n iterations |
| Space | O(n) — stack and result array |
Even though we iterate 2n times, each element enters and leaves the stack at most once, so the total work is O(n).
Edge Cases
# Single element — wraps to itself, no greater
assert nextGreaterElements([5]) == [-1]
# All same elements
assert nextGreaterElements([3, 3, 3]) == [-1, -1, -1]
# Strictly increasing — last has no greater, rest are obvious
assert nextGreaterElements([1, 2, 3]) == [2, 3, -1]
# Strictly decreasing — each wraps around to find a greater
assert nextGreaterElements([3, 2, 1]) == [-1, 3, 3]
# Two elements
assert nextGreaterElements([2, 1]) == [-1, 2]
When to Use This Pattern
Use the double-iteration monotonic stack when:
- The problem involves a circular array with next/previous greater/smaller
- You need to “wrap around” the end to the beginning
- Any problem that uses monotonic stack on a linear array but with circular semantics
The same trick works for:
- Next smaller element in circular array
- Previous greater element in circular array
- Any variant where the search should wrap
Common Mistakes
- Only iterating once — misses wraparound cases
- Pushing during the second pass — causes duplicates in the stack
- Forgetting
i % n— index out of bounds on the second pass - Using
>=instead of>— “greater” means strictly greater
Related Problems
| Problem | Key Difference |
|---|---|
| Next Greater Element I (LC 496) | Two arrays, no circular |
| Daily Temperatures (LC 739) | “How many days until warmer” |
| Largest Rectangle in Histogram (LC 84) | Next smaller from both sides |
| Circular Array Loop (LC 457) | Cycle detection, not next greater |
| Stock Span (LC 901) | Previous greater element |
Key Takeaways
- The circular array trick: iterate
2ntimes withi % nindexing - Only push indices during the first
niterations to avoid duplicates - The monotonic stack invariant is the same as the non-circular version
- Each element is pushed once and popped at most once: O(n) total
- This pattern extends naturally to any circular monotonic stack problem
Related articles
- DSA 132 Pattern — Monotonic Stack with Reverse Traversal (LeetCode 456)
Solve the 132 Pattern problem using a monotonic stack scanning right to left. Python solution tracking s3 candidates and s2 maximum, with detailed trace.
- DSA Basic Calculator I, II, III — Complete Expression Evaluation Guide
Solve Basic Calculator problems LeetCode 224, 227, and 772. Master stack-based expression evaluation with +, -, *, /, and parentheses in Python.
- DSA Largest Rectangle in Histogram Using Stack
Find the largest rectangle in a histogram using a monotonic stack in O(n). Detailed walkthrough, Python code, visual trace, and common pitfalls.
- DSA Maximal Rectangle in Binary Matrix
Find the maximal rectangle containing only 1s in a binary matrix. Builds on the largest rectangle in histogram technique with detailed explanation.