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 · 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:

actionmeaningPython
pushput x on topst.append(x)
popremove the topst.pop()
peeklook at the topst[-1]
empty?nothing insidenot 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:

  1. Walk from the right, so the right side is already summarised in the stack.
  2. 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.
  3. If anything is left, the top is the answer. Otherwise the answer is −1.
  4. 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.

index012
nums121
answer2-12

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

3Intuition: "circular" = "the array written twice"

The teacher's trick: whenever a question says circular, imagine the array copied right after itself:

index012345
doubled121121yellow = the copy

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

Doubt 1: if we don't save answers for indices 3, 4, 5, why process them at all?
→ 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.
Doubt 2: is copying once enough? What if the answer is further away?
→ 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.
Doubt 3: the copied 1 at index 3 is smaller than the 2 on the stack. Why push it?
→ 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.
Doubt 4: what would the brute force be?
→ 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

  1. arr = nums + nums, ans = [-1] * n, empty stack.
  2. For i from 2n−1 down to 0, with x = arr[i]:
  3. While the stack is not empty and top ≤ x → pop.
  4. If i < n (a real index) and the stack is not empty → ans[i] = top.
  5. Push x (always, real or copy).
  6. Return ans.

6Code (Python)

Doubled array version
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 ans

7Code line by line

linewhat it means
arr = nums + numsBuild the doubled array (length 2n). This costs O(n) extra space. Part B removes it.
ans = [-1] * nSize 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)

icurrentreal?what we pop (and why)what we pushstack AFTERanswer so far
51copynothing (empty)1[1][−1, −1, −1]
42copypop 1 (1 ≤ 2: hidden behind this 2)2[2][−1, −1, −1]
31copynothing: 2 > 1 (would be answer 2, but not saved)1[2, 1][−1, −1, −1]
21yespop 1 (equal, not greater) · top 2 > 1 → ans[2] = 21[2, 1][−1, −1, 2]
12yespop 1 (smaller) · pop 2 (equal) · stack empty → stays −12[2][−1, −1, 2]
01yesnothing: 2 > 1 → ans[0] = 21[2, 1][2, −1, 2]
end of the warm-up (after i=3)
2 (from idx 4)1 (from idx 3)
i=2 after popping the equal 1
2 → answer for idx 2
i=1 after both pops
(empty) → −1

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

RememberCircular → unroll the array twice. Run the normal right-to-left stack over 2n items. Save answers only when i < n. The copy half is just a warm-up for the stack.

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)012345
i % 3012012

So we loop i over 0 … 2n−1 and read nums[i % n]. No second array is needed.

4Building the logic

Doubt: why not just use 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

  1. ans = [-1] * n, empty stack.
  2. For i from 2n−1 down to 0: x = nums[i % n].
  3. Pop while top ≤ x.
  4. If i < n and the stack is not empty → ans[i] = top.
  5. Push x. Return ans.

6Code (Python)

Modulo version (final answer)
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 ans

7Code line by line

linewhat 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].

i0123456789
nums[i%5]1234312343yellow = the fake copy (warm-up)
icurrentwhat we pop (and why)what we pushstack AFTERanswer so far
93nothing (empty) · copy, no save3[3][−1, −1, −1, −1, −1]
84pop 3 (3 ≤ 4) · copy4[4]same
73nothing · copy3[4, 3]same
62nothing · copy2[4, 3, 2]same
51nothing · copy1[4, 3, 2, 1]same
43pop 1, pop 2 (smaller) · pop 3 (equal) · top 4 → ans[4] = 43[4, 3][−1, −1, −1, −1, 4]
34pop 3 (smaller) · pop 4 (equal) · empty → −14[4][−1, −1, −1, −1, 4]
23nothing: 4 > 3 → 43[4, 3][−1, −1, 4, −1, 4]
12nothing: 3 > 2 → 32[4, 3, 2][−1, 3, 4, −1, 4]
01nothing: 2 > 1 → 21[4, 3, 2, 1][2, 3, 4, −1, 4]
after the warm-up (i=5)
4321
i=4 after 3 pops
4 → answer
i=3: the maximum empties the stack
(empty) → −1

Notice: the largest value (4) always ends with −1, even in a circle. Nothing anywhere is bigger than it.

9Complexity & remember

RememberLoop 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)
loopi from n−1 to 0i from 2n−1 to 0
value readarr[i]nums[i % n] (or the doubled array)
pop rulesame: while st and st[-1] <= x: pop
save answerif stif i < n and st
pushsame: always push x
time / spaceO(n) / O(n)O(n) (≈ 2× the work) / O(n)
If you remember only 5 lines 1. Circular = the array written twice in a row.
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.
Mistakes to avoid ✗ using 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 answers
test it yourself (paste under either solution)
s = 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