DSA sheet · Stack · Monotonic stack pattern
Next Greater Element II (Circular)
This is the same question as Next Greater Element, with one twist: the array is circular. After the last element, you wrap around to the first one. The teacher's message is that the stack logic doesn't change at all. We only need a way to "see" the array twice: walk 2n steps instead of n, and only save answers for the real indices. Circular arrays come up often, so this trick is worth learning once and for all.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · Stacks, the monotonic stack, and what "circular" means
- Part A · Copy the array twice, then run the normal stack
- Part B · Same idea without copying: index % n
- Part C · Revision page
Part 0 · Before starting
Stack in 30 seconds
A stack is a pile where you only touch the top: LIFO = Last In, First Out. In Python we use a plain list:
| action | meaning | Python |
|---|---|---|
| push | put x on top | st.append(x) |
| pop | remove the top | st.pop() |
| peek | look at the top | st[-1] |
| empty? | nothing inside | not st |
All four are O(1). Never pop or peek an empty stack: it raises IndexError. That's why we always write while st and … and if st:. Drawings: left = bottom, right = top.
The monotonic stack (recap from Problem 2)
To find the next greater element (the first strictly bigger value to the right) for every index:
- Walk from the right, so the right side is already summarised in the stack.
- Pop while the top is
<= x. Those items are hidden behind x: x is closer and at least as tall, so they can never be anybody's answer again. - If anything is left, the top is the answer. Otherwise the answer is −1.
- Push x.
The stack is always strictly decreasing from bottom to top, the visible skyline to the right of the current index. Each item is pushed once and popped at most once → O(n).
What is a circular array?
Picture the numbers written around a clock face. Going "right" means going clockwise. After the last number you come back to the first one. So when we look for the next greater element and run out of numbers on the right, we continue from index 0.
index 0
(1)
↗ ↘
(1) ←─── (2)
index 2 index 1 clockwise: 0 → 1 → 2 → 0 → 1 …
Part A · Copy the array twice, then run the normal stack
LeetCode 503
1The question in simple words
Given a circular array nums, return for every index the next greater number: the first value strictly bigger than it, searching clockwise (to the right, and wrapping around to the start). If none exists anywhere, return −1.
- Index 0 (value 1): the next value 2 is bigger → 2.
- Index 1 (value 2): to the right is 1, then wrap to 1 (index 0). Nothing is bigger than 2 → −1.
- Index 2 (value 1): nothing on its right. In the non-circular problem the answer would be −1, but here we wrap: index 0 is 1 (not bigger), index 1 is 2 (bigger) → 2. This is the only answer that changed because of the circle.
Second example: [1, 2, 3, 4, 3] → [2, 3, 4, −1, 4]. The last 3 wraps around: 1 ✗, 2 ✗, 3 ✗ (equal), 4 ✓.
2What the constraints tell us
- LeetCode: 1 ≤ n ≤ 10⁴ → the array is never empty, but n = 1 is possible (answer
[-1], since nothing can be bigger than itself). - Values from −10⁹ to 10⁹ → negative numbers are allowed, even −1 itself. That's fine: in the answer, LeetCode defines −1 as "not found", and the comparisons work the same for negative values.
- n ≤ 10⁴ means even O(n²) ≈ 10⁸ might squeeze through, but the stack gives O(n), so we use it.
3Intuition: "circular" = "the array written twice"
The teacher's trick: whenever a question says circular, imagine the array copied right after itself:
Now, from index 2, "going right and wrapping" is the same as simply going right in the doubled array: you meet index 3 (a copy of index 0), then index 4 (a copy of index 1, value 2) ✓. Walking clockwise around the circle and walking straight through the doubled array read the same numbers in the same order.
So we can run the ordinary next-greater stack on the doubled array. The only care: answers are only needed for indices 0 … n−1. Indices n … 2n−1 are copies. We still process them, but only to fill the stack.
4Building the logic from the example
→ To build the stack. Walking from the right, the copy is visited first. By the time we reach the real index 2, the stack already holds the "skyline" of everything that comes after index 2 when you go around the circle. Without that warm-up, index 2 would see an empty stack and wrongly get −1.
→ Once is enough. Going clockwise from any index, you only need to look at the other n−1 elements before you're back at yourself. In the doubled array, every real index i (< n) has at least n−1 elements after it. A third copy would add nothing new.
→ Same reason as in Problem 2: it's the nearest candidate for whatever comes next on the left. If the next real value were 0, that 1 would be its answer, not the 2. Every value gets pushed once.
→ For each i, check the next n−1 positions
(i+1) % n, (i+2) % n, … and stop at the first bigger one. That's O(n²). The teacher goes straight to the stack here, since the brute-force idea is the same as in Problem 2.5Approach steps
arr = nums + nums,ans = [-1] * n, empty stack.- For
ifrom 2n−1 down to 0, withx = arr[i]: - While the stack is not empty and top ≤ x → pop.
- If
i < n(a real index) and the stack is not empty →ans[i] = top. - Push x (always, real or copy).
- Return
ans.
6Code (Python)
class Solution:
def nextGreaterElements(self, nums):
n = len(nums)
arr = nums + nums # the circle, unrolled twice
ans = [-1] * n # answers only for the real indices
st = []
for i in range(2 * n - 1, -1, -1): # right to left over 2n items
x = arr[i]
while st and st[-1] <= x: # hidden forever -> throw away
st.pop()
if i < n and st: # real index AND something bigger exists
ans[i] = st[-1]
st.append(x)
return ans7Code line by line
| line | what it means |
|---|---|
| arr = nums + nums | Build the doubled array (length 2n). This costs O(n) extra space. Part B removes it. |
| ans = [-1] * n | Size n, not 2n: we only answer for the original positions. |
| for i in range(2 * n - 1, -1, -1): | Last index of the doubled array is 2n−1. Go down to 0. |
| while st and st[-1] <= x: st.pop() | Exactly the Problem 2 rule: drop every top that is not strictly bigger. |
| if i < n and st: | New: the i < n check. For the copy half we only update the stack, never ans (and ans[i] would be out of range there anyway). |
| st.append(x) | Push every value, from both halves. |
8Dry run on [1, 2, 1] (doubled: 1 2 1 | 1 2 1)
| i | current | real? | what we pop (and why) | what we push | stack AFTER | answer so far |
|---|---|---|---|---|---|---|
| 5 | 1 | copy | nothing (empty) | 1 | [1] | [−1, −1, −1] |
| 4 | 2 | copy | pop 1 (1 ≤ 2: hidden behind this 2) | 2 | [2] | [−1, −1, −1] |
| 3 | 1 | copy | nothing: 2 > 1 (would be answer 2, but not saved) | 1 | [2, 1] | [−1, −1, −1] |
| 2 | 1 | yes | pop 1 (equal, not greater) · top 2 > 1 → ans[2] = 2 | 1 | [2, 1] | [−1, −1, 2] |
| 1 | 2 | yes | pop 1 (smaller) · pop 2 (equal) · stack empty → stays −1 | 2 | [2] | [−1, −1, 2] |
| 0 | 1 | yes | nothing: 2 > 1 → ans[0] = 2 | 1 | [2, 1] | [2, −1, 2] |
Look at the first picture: after the copy half, the stack already "knows" the values that follow index 2 around the circle (1, then 2). That's exactly what index 2 needed.
9Complexity & remember
- Time O(n): the loop runs 2n times. Each of the 2n values is pushed once and popped at most once, so at most 2n pops. About 4n steps in total, still linear.
- Space O(n): the stack, plus the doubled array (2n).
Part B · Same idea without copying: index % n
1The question
Same as Part A. We just want to avoid physically building nums + nums.
2What the constraints tell us
Same as Part A (n from 1 to 10⁴). Nothing new, so this part only changes how we read the values.
3Intuition: the copy is fake, so compute where it points
Index 3 of the doubled array is just a copy of index 0, index 4 of index 1, and index 5 of index 2. In general, doubled index i holds nums[i % n] (the remainder after dividing by n).
| i (0 … 2n−1) | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| i % 3 | 0 | 1 | 2 | 0 | 1 | 2 |
So we loop i over 0 … 2n−1 and read nums[i % n]. No second array is needed.
4Building the logic
nums[i] in the 2n loop?→ The teacher points out this bug: when i is 3, 4 or 5,
nums[i] is out of range (the real array only has indices 0–2). i % n folds those indices back to 0, 1, 2. (In Python, a large index raises IndexError, so the bug would show up right away.)Everything else (pop rule, push rule, the i < n check) is the same as Part A.
5Approach steps
ans = [-1] * n, empty stack.- For
ifrom 2n−1 down to 0:x = nums[i % n]. - Pop while top ≤ x.
- If
i < nand the stack is not empty →ans[i] = top. - Push x. Return
ans.
6Code (Python)
class Solution:
def nextGreaterElements(self, nums):
n = len(nums)
ans = [-1] * n
st = []
for i in range(2 * n - 1, -1, -1):
x = nums[i % n] # fake second copy: fold the index back
while st and st[-1] <= x:
st.pop()
if i < n and st: # save only for the real indices
ans[i] = st[-1]
st.append(x)
return ans7Code line by line
| line | what it means |
|---|---|
| for i in range(2 * n - 1, -1, -1): | Visit the circle twice, from the right. |
| x = nums[i % n] | The value at "doubled index" i, without building the doubled array. |
| while st and st[-1] <= x: st.pop() | Remove everything hidden behind x. |
| if i < n and st: ans[i] = st[-1] | The first half (as we walk, the second pass) writes the answers. |
| st.append(x) | x becomes the nearest candidate. |
8Dry run on [1, 2, 3, 4, 3]
n = 5, so i goes 9 → 0, and the value is nums[i % 5].
| i | current | what we pop (and why) | what we push | stack AFTER | answer so far |
|---|---|---|---|---|---|
| 9 | 3 | nothing (empty) · copy, no save | 3 | [3] | [−1, −1, −1, −1, −1] |
| 8 | 4 | pop 3 (3 ≤ 4) · copy | 4 | [4] | same |
| 7 | 3 | nothing · copy | 3 | [4, 3] | same |
| 6 | 2 | nothing · copy | 2 | [4, 3, 2] | same |
| 5 | 1 | nothing · copy | 1 | [4, 3, 2, 1] | same |
| 4 | 3 | pop 1, pop 2 (smaller) · pop 3 (equal) · top 4 → ans[4] = 4 | 3 | [4, 3] | [−1, −1, −1, −1, 4] |
| 3 | 4 | pop 3 (smaller) · pop 4 (equal) · empty → −1 | 4 | [4] | [−1, −1, −1, −1, 4] |
| 2 | 3 | nothing: 4 > 3 → 4 | 3 | [4, 3] | [−1, −1, 4, −1, 4] |
| 1 | 2 | nothing: 3 > 2 → 3 | 2 | [4, 3, 2] | [−1, 3, 4, −1, 4] |
| 0 | 1 | nothing: 2 > 1 → 2 | 1 | [4, 3, 2, 1] | [2, 3, 4, −1, 4] |
Notice: the largest value (4) always ends with −1, even in a circle. Nothing anywhere is bigger than it.
9Complexity & remember
- Time O(n): 2n loop steps, and at most 2n pushes and 2n pops in total.
- Space O(n): just the stack and the answer. No doubled array this time.
i from 2n−1 to 0, read nums[i % n], and save only when i < n. Everything else is the Problem 2 code.Part C · Revision page
| NGE I (Problem 2) | NGE II, circular (this page) | |
|---|---|---|
| loop | i from n−1 to 0 | i from 2n−1 to 0 |
| value read | arr[i] | nums[i % n] (or the doubled array) |
| pop rule | same: while st and st[-1] <= x: pop | |
| save answer | if st | if i < n and st |
| push | same: always push x | |
| time / space | O(n) / O(n) | O(n) (≈ 2× the work) / O(n) |
2. Loop 2n times from the right. Read
nums[i % n] so the index never goes out of range.3. The first n steps (the copy half) only build the stack.
4. Save
ans[i] only when i < n and the stack isn't empty.5. Pop rule, push rule and O(n) reasoning are unchanged from NGE I.
nums[i] with i ≥ n (index out of range)✗ writing
ans[i] for i ≥ n (out of range, and meaningless)✗ making
ans of size 2n and forgetting to cut it✗ looping only n times, which gives −1 for elements whose answer is "around the corner"
✗ popping only on
<, so equal values wrongly become answerss = Solution() print(s.nextGreaterElements([1, 2, 1])) # [2, -1, 2] print(s.nextGreaterElements([1, 2, 3, 4, 3])) # [2, 3, 4, -1, 4] print(s.nextGreaterElements([5])) # [-1] print(s.nextGreaterElements([3, 3, 3])) # [-1, -1, -1] print(s.nextGreaterElements([5, 4, 3, 2, 1])) # [-1, 5, 5, 5, 5]
Based on this video: Next Greater Element II | Circular Array | Monotonic Stack