DSA sheet · Stack · Monotonic stack pattern

Next Greater Element

This is the first problem of the stack topic and the first problem of the monotonic stack pattern. The teacher first writes the obvious two-loop answer, shows why it is too slow for the given limits, and then builds the stack answer step by step. If you understand why we pop and what the stack is remembering on this page, the next problems (circular array, daily temperatures, histogram…) become small changes of the same idea.

At the end she also explains how the LeetCode version (496, with two arrays nums1 and nums2) is just this same function plus a lookup.

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

What is a stack?

A stack is a pile of items where you can only touch the top. Think of a pile of plates: you put a new plate on top, and you take a plate from the top. You never pull a plate out from the middle.

This rule has a name: LIFO = Last In, First Out. The item that went in last is the first one to come out.

Unlike an array, a stack gives you no indexing: you can't ask for "the 3rd item". You only get three actions:

actionmeaningPython (a list used as a stack)cost
pushput an item on topst.append(x)O(1)
popremove the top item (and get it back)st.pop()O(1)
peek (top)look at the top item without removing itst[-1]O(1)
is empty?is there anything in the pile?if not st: / if st:O(1)
Golden safety ruleAlways check that the stack is not empty before st.pop() or st[-1]. On an empty list, pop() raises IndexError, and so does st[-1]. That is why every loop on this page starts with while st and …: the st and part guards the peek that comes after it.

In this notebook, a stack is always drawn left = bottom, right = top, like [4, 3, 1] where 1 is on top. In the vertical pictures, the top is at the top, drawn in red.

Why do we need a stack at all?

Many array problems ask, for each position, "what is the first bigger (or smaller) item after me?" or "before me?". The simple way is a second loop that walks forward or backward from every position. Two loops over n items cost about n² steps.

A stack lets us remember the useful items we've already passed, so we never walk back over them. That turns n² into n.

How to spot a stack problem (the teacher's main tip)If your brute force looks like "for every i, move j forward (or backward) until you find a greater or smaller element", think stack. One loop + a stack replaces the two loops.

What is a monotonic stack?

Monotonic means "always going one way". A monotonic stack is a normal stack where we keep the items in sorted order from bottom to top, either always decreasing or always increasing.

How do we keep it sorted? Before pushing a new item, we pop every item on top that would break the order. In this problem:

What does the stack remember? Only the items that can still be an answer for someone in the future. Every item we pop is one we have proved can never be an answer again (Part B, step 4 explains the proof). That is the whole trick.


Part A · Brute force: two loops

GFG "Next Greater Element" (also the core of LeetCode 496)

1The question in simple words

You get an array. For every element, find the next greater element: the first element to its right that is strictly bigger than it. If no such element exists, write −1.

index0123
arr1324
answer344-1

"Greater" means strictly greater. An equal value doesn't count: for [3, 3], the first 3 gets −1, not 3.

2What the constraints tell us

3Intuition

Stand on one element with your left finger (i). Walk your right finger (j) to the right, one step at a time, and stop at the first bigger value. Write that value down for i. Then move i one step and start over.

4Building the logic, the way the teacher does

Doubt 1: should j start at i or at i + 1?
→ At i + 1. Comparing an element with itself is useless: 1 is never greater than 1. We only care about items to the right.
Doubt 2: why fill the answer with −1 at the start?
→ Because −1 is the answer whenever no bigger item is found. If we pre-fill every slot with −1, we only need to overwrite a slot when we do find a bigger item. Nothing else needs handling. The last element always keeps its −1, since nothing is on its right.
Doubt 3: the teacher stops i at the second-last index. Why?
→ j starts at i + 1. For the last index, i + 1 is outside the array, so there's nothing to check. Its answer is already −1 from the pre-fill. (In Python, range(n) would also work, since the inner loop would just run zero times. range(n - 1) just says the idea out loud.)
Doubt 4: once I find a bigger value, should j keep going?
→ No, break. We want the first bigger value. Anything further right is not "next". Without the break, a later bigger value could overwrite the correct answer.

5Approach steps

  1. Make ans of size n filled with −1.
  2. For each i from 0 to n − 2:
  3. For each j from i + 1 to n − 1: if arr[j] > arr[i], set ans[i] = arr[j] and break.
  4. Return ans.

6Code (Python)

Brute force, O(n²)
class Solution:
    def nextLargerElement(self, arr):
        n = len(arr)
        ans = [-1] * n                      # default: no greater element
        for i in range(n - 1):              # last index keeps -1
            for j in range(i + 1, n):       # look only to the right
                if arr[j] > arr[i]:         # strictly greater
                    ans[i] = arr[j]
                    break                   # first one found = the "next" one
        return ans

The teacher's Java version returns an ArrayList, so she adds values to it instead of using indexes. In Python we simply make a list of the right size and assign by index.

7Code line by line

linewhat it means
ans = [-1] * nAssume "no greater element" for everyone. We'll overwrite where we find one.
for i in range(n - 1):The element we're finding an answer for. The last one is skipped, because nothing is on its right.
for j in range(i + 1, n):Scan everything to the right of i, nearest first.
if arr[j] > arr[i]:Is this one strictly bigger? (Equal does not count.)
ans[i] = arr[j] breakSave it, then stop scanning: the first bigger value is the answer.

8Dry run on [1, 3, 2, 4]

iarr[i]j checksfoundans after
01j=1: 3 > 1 ✓3[3, −1, −1, −1]
13j=2: 2 ✗ · j=3: 4 > 3 ✓4[3, 4, −1, −1]
22j=3: 4 > 2 ✓4[3, 4, 4, −1]
34not visited, keeps −1[3, 4, 4, −1] ✓

9Complexity & remember

The break helps sometimes, but think about the worst case: a decreasing array like 9 8 7 6 5 4 3 2 1. Nobody has a bigger item on the right, so j never breaks early. For 9 it scans 8 items, for 8 it scans 7, and so on. Total ≈ n·(n−1)/2 → O(n²).

Remember the brute forceTwo loops: for each i, scan right until the first bigger value, then break. Correct but O(n²). The shape "j runs ahead looking for a greater/smaller value" is the signal to use a stack.

Part B · Optimal: monotonic stack from the right

1The question again, with a new goal

Same question, same answer. Now we want one loop instead of two, so the cost drops from O(n²) to O(n).

2What the constraints tell us

3Intuition: walk from the right and keep a "skyline"

The answer for an element depends on what lies to its right. If we walk left to right, we haven't seen the right side yet, so we'd have to scan forward again. That's the brute force.

So the teacher flips the direction: start from the last index and walk left. When we reach index i, every element to its right has already been seen. We just need to keep a short summary of them. That summary is the stack.

Picture standing at i and looking right at a row of buildings. A short building hidden behind a taller one closer to you can never be "the first taller one" for you or anyone further left. Only the visible skyline matters, and the stack is that skyline: nearest building on top, getting taller as you go down.

For the last element there's nothing on the right, so its answer is −1. That's why the answer list is pre-filled with −1 again.

4Building the logic from the example

Array [1, 3, 2, 4], walking from the right.

At 4 (index 3)

The stack is empty, so there's nothing on the right → answer −1. Now push 4: it might be the answer for someone on the left. Stack: [4].

At 2 (index 2)

The stack is not empty, so look at the top: 4. Is 4 bigger than 2? Yes. So 4 is the answer for 2. We don't need to look any deeper into the stack: the top is the nearest remaining candidate, so if it's bigger, it's the first bigger one.

Then push 2. Even though 2 is small, it could still be the answer for some smaller element further left (for example a 0). Stack: [4, 2].

At 3 (index 1): the first pop

Top is 2. Is 2 bigger than 3? No. So 2 is not the answer for 3. The teacher gives two reasons to pop 2 (throw it away) instead of just skipping it:

  1. It's useless for 3, and popping it is O(1). We look at the next item without walking back through the array.
  2. It's useless for everyone to the left of 3 too. Take any future element z on the left. If 2 were bigger than z, then 3 is also bigger than z (because 3 > 2), and 3 is closer to z. So 3 would be found first, never 2. In skyline words: 2 is now hidden behind 3.

After popping 2, the top is 4. Is 4 bigger than 3? Yes → answer for 3 is 4. Push 3. Stack: [4, 3].

At 1 (index 0)

Top is 3, and 3 > 1 → answer 3, no pop. Push 1. Stack: [4, 3, 1]. Final answer [3, 4, 4, −1] ✓.

Why a popped item can never be an answer again (the key proof) We pop y when we're standing at x and y ≤ x. In the array, x sits between every future element z (which is further left) and y.
If y > z, then x ≥ y > z, so x is also bigger than z and closer to it. So z's first bigger element is x or something even closer, never y.
If y ≤ z, then y isn't bigger than z at all.
Either way y can't be the answer for anyone, so throwing it away loses nothing.

The equal case: why pop when top == current?

The teacher changes the example to show this. Suppose the array were [3, 1, 3, 2, 4], so there's another 3 left of the 1. When we reach that left 3, the stack is [4, 3, 1]:

So the pop condition is top <= current, not top < current.

Doubt 1: why while and not if for popping?
→ One new element may hide many old ones. At the left 3 above, we had to pop twice (1, then the old 3). An if would pop only once and leave a wrong item on top. We keep popping until the top is bigger, or the stack is empty.
Doubt 2: why check "stack not empty" twice in the code?
→ Once inside the while (before we peek to compare), and once more before reading the answer from the top. After popping, the stack may be empty. That means nothing on the right is bigger, so we leave −1. Peeking an empty list would crash.
Doubt 3: why do we push the current element every time, even when it's small?
→ Because it's the closest item for the next element on the left, so it's always a candidate. A small value like 1 can still be the answer for a 0 that might come next. Whether it's smaller or bigger than the top, it always goes in once.
Doubt 4: what if the question asked for the next greater element on the left?
→ Same code, but loop from index 0 to n−1 (left to right). The rule: walk from the side where the answer lives, so that side has already been summarised in the stack.

What the stack holds, in one sentence

At any moment, the stack holds, from top to bottom, the elements to the right of i that are not hidden by a closer element that's at least as tall. Their values are strictly increasing from top to bottom. The top is always the nearest candidate.

5Approach steps

  1. Make ans of size n filled with −1, and an empty stack.
  2. For i from n − 1 down to 0, with x = arr[i]:
  3. While the stack is not empty and its top ≤ x → pop (the top is hidden forever).
  4. If the stack is still not empty → ans[i] = top.
  5. Push x.
  6. Return ans.

6Code (Python)

Optimal, monotonic stack, O(n)
class Solution:
    def nextLargerElement(self, arr):
        n = len(arr)
        ans = [-1] * n
        st = []                              # values; bottom -> top is strictly decreasing
        for i in range(n - 1, -1, -1):       # walk from the RIGHT end
            x = arr[i]
            while st and st[-1] <= x:        # top is not greater -> useless forever
                st.pop()
            if st:                           # whatever is left on top is the answer
                ans[i] = st[-1]
            st.append(x)                     # x is the nearest candidate for the left side
        return ans

7Code line by line

linewhat it means
ans = [-1] * nDefault answer for everyone. It stays −1 whenever the stack ends up empty.
st = []The "skyline" of elements to the right of i. Top = nearest.
for i in range(n - 1, -1, -1):Go from the last index down to 0. The stop value −1 is excluded, so 0 is included.
while st and st[-1] <= x: st.pop()Throw away every top that is not strictly bigger than x. st and comes first so we never peek an empty list.
if st: ans[i] = st[-1]The survivor on top is bigger than x and is the nearest such item → the next greater element.
st.append(x)x goes in no matter what. It hides smaller items for everyone on its left.

8Dry run

Example 1: [1, 3, 2, 4]

icurrentwhat we pop (and why)what we pushstack AFTER (bottom → top)answer so far
34nothing (stack empty) → answer stays −14[4][−1, −1, −1, −1]
22nothing: top 4 > 2 → answer 42[4, 2][−1, −1, 4, −1]
13pop 2 (2 ≤ 3, hidden behind 3) · then top 4 > 3 → answer 43[4, 3][−1, 4, 4, −1]
01nothing: top 3 > 1 → answer 31[4, 3, 1][3, 4, 4, −1]
at i=1, before popping
42 ≤ 3 → pop
at i=1, after pop + push
43
the end
431

Read each picture bottom to top: the values always decrease going up. That's the "monotonic" part.

Example 2 (the equal case): [3, 1, 3, 2, 4]

index01234
arr31324yellow = the extra 3 the teacher adds
icurrentwhat we pop (and why)what we pushstack AFTERanswer so far
44nothing (empty) → −14[4][_, _, _, _, −1]
32nothing: 4 > 2 → 42[4, 2][_, _, _, 4, −1]
23pop 2 (smaller) · 4 > 3 → 43[4, 3][_, _, 4, 4, −1]
11nothing: 3 > 1 → 31[4, 3, 1][_, 3, 4, 4, −1]
03pop 1 (smaller) · pop 3 (equal is not greater) · 4 > 3 → 43[4, 3][4, 3, 4, 4, −1]
i=0, before the pops
431
after popping 1 and the old 3
4 → answer
after pushing the new 3
43

9Complexity & remember

It looks like a loop inside a loop, so you might guess O(n²). The teacher explains why it's actually about 2n:

Time O(n + n) = O(2n) = O(n). Space O(n) for the stack (worst case an increasing array, where nothing is ever popped, e.g. 1 2 3 4 from the right leaves [4, 3, 2, 1]).

Remember the NGE template Walk from the right. while st and st[-1] <= x: pop → if st: ans = st[-1] → push x.
The stack is a skyline: bottom → top strictly decreasing. A popped item is hidden by a closer, taller one, so it can never be an answer again.

The 4 standard variants (same code, two knobs)

problemloop directionpop while top is…stack bottom → top
next greater to the right (this page)right → left<= xdecreasing
next greater to the left (previous greater)left → right<= xdecreasing
next smaller to the rightright → left>= xincreasing
next smaller to the left (previous smaller)left → right>= xincreasing

Knob 1: walk from the side where the answer lives. Knob 2: pop everything that is "not good enough" to be the answer.


Part C · LeetCode 496 version (nums1 inside nums2)

LeetCode 496. The teacher explains this version at the end of the video but doesn't code it. The code below is that explanation written out.

1The question in simple words

You get two arrays. nums2 is the real array. nums1 is a list of some values picked from nums2. All values are different. For each value in nums1, find where it sits in nums2 and return its next greater element in nums2 (or −1).

nums21342
NGE34-1-1exactly what Part B computes
nums1412
answer-13-1look up 4 → −1, 1 → 3, 2 → −1

2What the constraints tell us

3Intuition

Step 1: run the normal next-greater function on nums2. Step 2: answer each query in nums1 by looking it up. The only new piece is the dictionary that connects a value to its answer.

4Building the logic

Doubt: why a dictionary and not the answer list from Part B?
→ The Part B list is ordered by position in nums2, but nums1 gives us values. We'd have to search nums2 for each value's position (slow). A dict {value: next greater} answers each query in O(1). It's safe because the values are unique.

5Approach steps

  1. Walk nums2 from the right with the Part B stack. For each value x, store nge[x] = the top (or −1).
  2. Return [nge[x] for x in nums1].

6Code (Python)

LeetCode 496
class Solution:
    def nextGreaterElement(self, nums1, nums2):
        nge = {}                             # value -> its next greater in nums2
        st = []
        for x in reversed(nums2):            # same walk as Part B, from the right
            while st and st[-1] <= x:
                st.pop()
            nge[x] = st[-1] if st else -1
            st.append(x)
        return [nge[x] for x in nums1]       # answer each query by lookup

7Code line by line

linewhat it means
for x in reversed(nums2):Same right-to-left walk. We don't need the index, only the value, because the dict key is the value.
nge[x] = st[-1] if st else -1Top of the stack, or −1 if the stack is empty.
return [nge[x] for x in nums1]One O(1) lookup per query.

8Dry run (nums2 = [1, 3, 4, 2])

xwhat we pop (and why)pushstack AFTERnge so far
2nothing (empty)2[2]{2: −1}
4pop 2 (2 ≤ 4)4[4]{2: −1, 4: −1}
3nothing: 4 > 33[4, 3]… 3: 4
1nothing: 3 > 11[4, 3, 1]… 1: 3

Queries [4, 1, 2] → [−1, 3, −1] ✓

9Complexity & remember

Remember LC 496Build the NGE map for nums2 with the usual stack, then look up each nums1 value.

Part D · Revision page

Brute forceMonotonic stack
ideafor each i, scan right until the first bigger valuewalk from the right, keep only the "visible" candidates
loopstwo nested loops, inner one breaks earlyone for loop + a while that pops (each item at most once)
worst casedecreasing array → full scansevery item pushed once, popped at most once
timeO(n²) → TLE at n = 10⁶O(2n) = O(n)
spaceO(1)O(n) stack
momentwhat the stack holds
just before handling index ithe unhidden elements to the right of i, nearest on top, values strictly increasing from top to bottom
after the popstop (if any) = the first element right of i that is strictly bigger → the answer
after the pushthe same skyline, now seen from position i − 1
If you remember only 5 lines 1. "For each i, j runs ahead to find a greater/smaller value" → use a stack.
2. The answer lives on the right → walk from the right.
3. Pop while top <= x (equal is not greater). Use while, not if.
4. If the stack isn't empty, its top is the answer. Otherwise −1. Then push x.
5. Each element is pushed once and popped at most once → O(n).
Mistakes to avoid ✗ peeking st[-1] without checking the stack is not empty
✗ popping only when top < x, which gives a wrong answer when values repeat
✗ using if instead of while for the pops
✗ forgetting to push x when the stack top was bigger
✗ walking left to right for a "next on the right" question
✗ in the brute force: forgetting the break (a later value overwrites the first one)
test it yourself (paste under the Part B solution)
s = Solution()
print(s.nextLargerElement([1, 3, 2, 4]))       # [3, 4, 4, -1]
print(s.nextLargerElement([3, 1, 3, 2, 4]))    # [4, 3, 4, 4, -1]
print(s.nextLargerElement([4, 3, 2, 1]))       # [-1, -1, -1, -1]
print(s.nextLargerElement([5, 5, 5]))          # [-1, -1, -1]
print(s.nextLargerElement([7]))                # [-1]

Based on this video: Next Greater Element | Monotonic Stack