Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

Monotonic Stack Pattern

What is Monotonic Stack?

Monotonic Stack is a stack where the elements are always maintained in sorted order — either strictly increasing from bottom to top (monotonic increasing) or strictly decreasing (monotonic decreasing). Every time you push a new element, you first pop off anything that violates the ordering invariant. Think of it like a bouncer at a club with a height rule: “No one shorter than the person behind you is allowed to stay.” When a tall person arrives, the bouncer removes everyone shorter who is already in line. This “removal event” is exactly when we record an answer — the element being removed just found its “next greater element.” This pattern transforms what looks like an O(n^2) “for each element scan the rest of the array” problem into a clean O(n) solution, because every element is pushed and popped at most once.

Pattern Recognition Cheatsheet

These exact problem-statement keywords should make you reach for a monotonic stack within 30 seconds. Recognizing the trigger phrases is half the battle.
Keyword / Shape in ProblemStack Direction (bottom-to-top)What You Record
”next greater element”, “first larger value to the right”Decreasing stackPop’s value when popped
”next smaller element”Increasing stackPop’s value when popped
”previous greater”, “leftmost larger”Decreasing, read on pushStack top before push
”previous smaller”, “leftmost smaller”Increasing, read on pushStack top before push
”trap rain water”Decreasing stackWater = min(walls) - bottom on each pop
”histogram area”, “largest rectangle”Increasing stackArea = height * (i - new_top - 1)
“stock span”, “days since price was higher”Decreasing stack of (price, span)Accumulated span on pop
”daily temperatures”, “days until warmer”Decreasing stack of indicesDistance i - popped_index
”sum of subarray minimums”Two passes (increasing both ways)Each element’s left-right span as min
”remove K digits”, “smallest number after deletion”Increasing stackGreedy character-level pop
”132 pattern”, “find arr[i] less than arr[k] less than arr[j] for i less than j less than k”Decreasing stack right-to-leftTrack candidate “third”
Mental shortcut: the stack direction is opposite to what you are searching for. Looking for next greater? Keep a decreasing stack — a larger arrival triggers resolution. Looking for next smaller? Keep an increasing stack. Memorize this one rule and you will never pick the wrong direction.

Reusable Template Code

Two skeletons — one for “next” problems (resolve on pop) and one for “previous” problems (resolve on push) — cover every monotonic-stack variant.
# Skeleton 1: NEXT greater (or smaller) -- resolve on POP
def next_greater(nums):
    n = len(nums)
    result = [-1] * n
    stack = []                              # store INDICES
    for i in range(n):
        while stack and nums[stack[-1]] < nums[i]:
            j = stack.pop()
            result[j] = nums[i]             # popped index found its answer
        stack.append(i)
    return result                           # leftover indices stay -1

# Skeleton 2: PREVIOUS smaller (or greater) -- resolve on PUSH
def previous_smaller(nums):
    n = len(nums)
    result = [-1] * n
    stack = []                              # increasing
    for i in range(n):
        while stack and nums[stack[-1]] >= nums[i]:
            stack.pop()
        if stack:
            result[i] = nums[stack[-1]]     # whatever survives is the answer
        stack.append(i)
    return result

# Skeleton 3: Histogram-style "compute on pop" with sentinel
def histogram_area(heights):
    stack = []
    max_area = 0
    heights = heights + [0]                 # sentinel forces final flush
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            top = stack.pop()
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, heights[top] * width)
        stack.append(i)
    return max_area

When to Use

Next Greater Element

Find next larger element for each position

Previous Smaller

Find previous smaller element

Histogram Problems

Largest rectangle, trapping water

Stock Span

Days since last higher price

Pattern Variations

1. Next Greater Element

For each position, find the first element to its right that is larger. The brute-force approach scans rightward for every element (O(n^2)). With a monotonic decreasing stack, each element is pushed and popped at most once, giving O(n) total. Key insight: The stack holds indices of elements that have not yet found their “next greater.” When a new element arrives that is larger than the stack top, we have found the answer for that stack element. Complexity: O(n) time, O(n) space for the stack and result array.
def next_greater_element(nums):
    """For each element, find the next element to its right that is larger.
    Return -1 if no such element exists.
    
    Walkthrough with [2, 1, 2, 4, 3]:
      i=0: stack=[]       -> push 0.           stack=[0]
      i=1: nums[1]=1 < nums[0]=2 -> push 1.   stack=[0,1]
      i=2: nums[2]=2 > nums[1]=1 -> pop 1, result[1]=2.
           nums[2]=2 == nums[0]=2 (not >), stop. push 2.  stack=[0,2]
      i=3: nums[3]=4 > nums[2]=2 -> pop 2, result[2]=4.
           nums[3]=4 > nums[0]=2 -> pop 0, result[0]=4.
           push 3.  stack=[3]
      i=4: nums[4]=3 < nums[3]=4 -> push 4.   stack=[3,4]
    Remaining in stack have no next greater -> stay -1.
    Result: [4, 2, 4, -1, -1]
    """
    n = len(nums)
    result = [-1] * n
    stack = []  # Indices of elements waiting for their next greater
    
    for i in range(n):
        # Pop all elements smaller than the current -- they found their answer
        while stack and nums[i] > nums[stack[-1]]:
            idx = stack.pop()
            result[idx] = nums[i]
        stack.append(i)
    
    return result

2. Previous Smaller Element

The mirror of “next greater” — for each element, find the nearest element to its left that is strictly smaller. We maintain a monotonic increasing stack (bottom to top). Before pushing the current element, we pop anything greater than or equal to it; whatever remains on top is the previous smaller element. Complexity: O(n) time, O(n) space.
def previous_smaller(nums):
    """For each element, find the nearest smaller element to its left.
    Return -1 if none exists.
    
    Example: [3, 5, 2, 7, 4]
      i=0: stack empty -> result[0] = -1.  push 0.
      i=1: nums[0]=3 < 5 -> result[1] = 3. push 1.
      i=2: pop 1 (5 >= 2), pop 0 (3 >= 2) -> stack empty, result[2] = -1. push 2.
      i=3: nums[2]=2 < 7 -> result[3] = 2. push 3.
      i=4: pop 3 (7 >= 4), nums[2]=2 < 4 -> result[4] = 2. push 4.
    Result: [-1, 3, -1, 2, 2]
    """
    n = len(nums)
    result = [-1] * n
    stack = []  # Monotonic increasing stack of indices
    
    for i in range(n):
        # Pop elements that are NOT smaller than the current element
        while stack and nums[stack[-1]] >= nums[i]:
            stack.pop()
        
        # Stack top (if any) is the previous smaller element
        if stack:
            result[i] = nums[stack[-1]]
        
        stack.append(i)
    
    return result

3. Largest Rectangle in Histogram

This is the canonical hard monotonic-stack problem. The idea: for each bar, find how far left and right it can extend as the shortest bar. The rectangle area for that bar is height * width. A monotonic increasing stack (by height) lets us compute this efficiently. When we encounter a bar shorter than the stack top, we pop and calculate the area for the popped bar — we know its right boundary (current index) and its left boundary (the new stack top). Anything left in the stack after the loop extends all the way to the right end. Complexity: O(n) time, O(n) space. Interview tip: This problem is the building block for “Maximal Rectangle” in a 2D matrix — you build a histogram per row and run this algorithm on each.
def largest_rectangle_area(heights):
    """Find the area of the largest rectangle in a histogram.
    
    Example: heights = [2, 1, 5, 6, 2, 3]
    The largest rectangle has area 10 (height 5, spanning indices 2-3).
    """
    stack = []   # Stack of (start_index, height) -- monotonic increasing by height
    max_area = 0
    
    for i, h in enumerate(heights):
        start = i  # Track how far back this height can extend
        # Pop bars taller than current -- they can no longer extend right
        while stack and stack[-1][1] > h:
            idx, height = stack.pop()
            max_area = max(max_area, height * (i - idx))  # Width = i - idx
            start = idx  # Current bar can extend back to where popped bar started
        stack.append((start, h))
    
    # Remaining bars in the stack extend all the way to the right edge
    for idx, height in stack:
        max_area = max(max_area, height * (len(heights) - idx))
    
    return max_area

4. Daily Temperatures

A direct application of the “next greater element” pattern, except instead of recording the value of the next greater element, we record the distance (number of days to wait). The stack holds indices of days still waiting for a warmer day. Complexity: O(n) time, O(n) space.
def daily_temperatures(temperatures):
    """For each day, find how many days until a warmer temperature.
    Return 0 if no warmer day exists.
    
    Example: [73, 74, 75, 71, 69, 72, 76, 73]
    Result:  [ 1,  1,  4,  2,  1,  1,  0,  0]
    """
    n = len(temperatures)
    result = [0] * n
    stack = []  # Indices of days still waiting for a warmer day
    
    for i in range(n):
        # Current day is warmer than days on the stack -- resolve them
        while stack and temperatures[i] > temperatures[stack[-1]]:
            idx = stack.pop()
            result[idx] = i - idx  # Distance in days
        stack.append(i)
    
    # Days remaining in the stack never found a warmer day -> result stays 0
    return result

5. Trapping Rain Water

The stack-based approach processes water layer by layer (horizontally), unlike the two-pointer approach which works column by column. When we encounter a bar taller than the stack top, we know water is trapped between the current bar and the bar beneath the popped element. The water volume for that layer is width * bounded_height. Complexity: O(n) time, O(n) space. Interview tip: This problem has three valid approaches (stack, two-pointer, prefix-max arrays). Being able to discuss all three and their trade-offs signals strong problem-solving breadth. The two-pointer approach uses O(1) space and is covered in the Two Pointers chapter.
def trap(height):
    """Calculate trapped rainwater using a monotonic stack.
    
    Strategy: maintain a decreasing stack. When we find a bar taller than
    the stack top, water is trapped between the current bar and the bar
    under the popped element. We compute water layer by layer.
    
    Example: [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] -> 6 units of water
    """
    stack = []   # Indices forming a decreasing sequence of heights
    water = 0
    
    for i, h in enumerate(height):
        while stack and h > height[stack[-1]]:
            bottom = stack.pop()       # The "valley floor"
            if not stack:
                break                  # No left wall -> water spills out
            
            # Water trapped between left wall (stack top) and right wall (i)
            width = i - stack[-1] - 1
            bounded_height = min(h, height[stack[-1]]) - height[bottom]
            water += width * bounded_height
        
        stack.append(i)
    
    return water

How to Choose Increasing vs Decreasing

The most confusing decision for beginners is which monotonic direction to use. Here is a simple rule:
You Want To FindStack DirectionPop When
Next GreaterDecreasing (bottom-to-top)Current element > stack top
Next SmallerIncreasing (bottom-to-top)Current element less than stack top
Previous GreaterDecreasing, record on pushAfter popping, stack top is the answer
Previous SmallerIncreasing, record on pushAfter popping, stack top is the answer
Memory trick: “Next” problems resolve on pop (the popped element found its answer). “Previous” problems resolve on push (the current element looks at whatever is left on the stack top after popping).

Worked LeetCode Problems: Brute Force vs Optimized

The four canonical monotonic-stack problems. Each one teaches a distinct twist on the same skeleton.

LC 496: Next Greater Element I (Easy)

Problem. Given two arrays where nums1 is a subset of nums2, find the next greater element in nums2 for each element in nums1. Brute force (O(n * m)). For each nums1[i], find it in nums2 and scan rightward. Slow. Optimized monotonic stack + map (O(n + m)). Pre-compute next greater for every element of nums2 using a monotonic decreasing stack, store results in a map, then look up nums1 answers in O(1) each.
def next_greater_element(nums1, nums2):
    nge = {}                                  # value -> next greater
    stack = []
    for x in nums2:
        while stack and stack[-1] < x:
            nge[stack.pop()] = x              # popped value's answer is x
        stack.append(x)
    # Anything left has no next greater
    return [nge.get(v, -1) for v in nums1]
Why this works. The stack acts as a “waiting room” — values sit there until a larger value arrives. The amortized cost is O(1) per element because each value is pushed once and popped at most once.

LC 739: Daily Temperatures (Medium)

Problem. For each day, return how many days until a warmer temperature, or 0 if none. Brute force (O(n^2)). For each day, scan forward. Optimized monotonic stack (O(n)). Stack of indices — not values — so we can compute the day-distance.
def daily_temperatures(temps):
    n = len(temps)
    result = [0] * n
    stack = []                                # indices waiting for a warmer day
    for i, t in enumerate(temps):
        while stack and temps[stack[-1]] < t:
            j = stack.pop()
            result[j] = i - j                 # distance, not value
        stack.append(i)
    return result
The “indices not values” lesson. We need i - j to compute the distance, so we cannot afford to throw away positional information. This is the rule for the entire pattern: when in doubt, push indices.

LC 84: Largest Rectangle in Histogram (Hard)

Problem. Given an array of bar heights, find the area of the largest axis-aligned rectangle that fits within the histogram. Brute force (O(n^2)). For each pair (i, j), find the minimum height in [i, j] and compute area. Optimized monotonic increasing stack (O(n)). When we encounter a bar shorter than the stack top, the popped bar’s rectangle is finalized: its height is heights[popped], its width spans from one past the new stack top up to (but not including) the current index.
def largest_rectangle(heights):
    stack = []
    max_area = 0
    heights = heights + [0]                   # sentinel forces flush at end
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            top = stack.pop()
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, heights[top] * width)
        stack.append(i)
    return max_area
The width formula trap. Width is i - stack[-1] - 1, NOT i - top. The reason: previously-popped bars between stack[-1] and i were all taller than the popped bar, so the popped bar’s rectangle extends through them. Get this wrong and you systematically under-count area.

LC 42: Trapping Rain Water (Hard) — Stack Approach

Problem. Given an elevation map, compute total trapped rainwater. Brute force (O(n^2)). For each position, find max-left and max-right, water at i = min(max_left, max_right) - height[i]. Optimized — there are three valid approaches. Two-pointer (O(1) space), prefix arrays (O(n) space, easiest), and monotonic stack (O(n) space, processes water layer-by-layer). Mention all three; pick two-pointer in production unless asked otherwise. Monotonic decreasing stack (O(n)). When a taller bar arrives, water is trapped between the current bar (right wall), the bar below the popped element (left wall), and the popped element itself (valley floor).
def trap(height):
    stack = []
    water = 0
    for i, h in enumerate(height):
        while stack and h > height[stack[-1]]:
            bottom = stack.pop()
            if not stack: break               # no left wall -> water escapes
            width = i - stack[-1] - 1
            bounded = min(h, height[stack[-1]]) - height[bottom]
            water += width * bounded
        stack.append(i)
    return water
Mental model. The stack approach computes water in horizontal layers (rectangle by rectangle), while the two-pointer approach computes it in vertical columns. Both arrive at the same total via different decompositions — a beautiful demonstration of how data-structure choice changes the algorithm’s mental model without changing its asymptotic cost.

Caveats and Traps

Bugs Every Monotonic Stack Problem Hides:
  1. Increasing vs decreasing stack — pick by what you are SEARCHING for, not what the stack looks like. “Next greater” needs a decreasing stack; “next smaller” needs an increasing one. The mistake is to read the problem and reach for the matching adjective (“greater means increasing”) — exactly backward.
  2. Stack stores indices, NOT values, when distance is needed. Daily Temperatures, Largest Rectangle, Trap Rain Water — they all need i - j or i - stack[-1] - 1. Push values and you lose the positional info.
  3. Off-by-one in width calculations. Width on pop is i - stack[-1] - 1 (after popping), not i - popped_index. The previously-popped bars contribute to the width because they were taller than the current popped bar.
  4. Forgetting elements left on the stack. “Next greater” leaves elements without an answer (default to -1). Largest Rectangle leaves bars whose right boundary is the array end. Use a sentinel value (append 0 for histogram, inf for next-smaller) to force the flush.
  5. Strict less-than vs less-than-or-equal in the pop condition. With duplicates, this changes the semantics: < on pop means “pop until strictly smaller arrives” (kept bigger-or-equal stay); <= means “pop until strictly larger arrives” (only stay if strictly larger). For Sum of Subarray Minimums, the rule is to use < on one side and <= on the other to avoid double-counting subarrays where duplicate minimums appear.
  6. Using the Stack class in Java. Synchronized, slower, legacy. Always Deque<Integer> stack = new ArrayDeque<>();.
  7. Misunderstanding amortization. Saying “the algorithm is O(n^2) because there is a while-loop inside the for-loop.” The total pops across all iterations cannot exceed n because each element is pushed exactly once. The while-loop is misleading visually; trust the proof.
Solutions and Idioms — Paired with the Caveats Above:
  1. Direction mantra: “I want next greater, so I keep a decreasing stack — a larger element on arrival is the trigger.” Speak it before coding.
  2. Default to indices. Push i, recover the value via nums[stack[-1]]. You can always derive the value from the index, but never the reverse.
  3. Width incantation: “After popping j, the new stack top is stack[-1] (or none). Width is i - stack[-1] - 1 — minus one because both ends are exclusive.”
  4. Sentinel trick: append a value that guarantees a final flush. Use 0 for histogram (always shorter than any positive bar) or float('inf') / Integer.MAX_VALUE for “next smaller” problems.
  5. For duplicates with sum-of-subarray problems: strict < going one direction, <= going the other. This is the contribution-technique idiom (see LC 907).
  6. Java idiom: Deque<Integer> stack = new ArrayDeque<>(); with push, pop, peek. Never Stack<Integer>.
  7. Amortization proof sentence: “Each element is pushed exactly once and popped at most once across the entire traversal. Total operations are bounded by 2n, so the algorithm is O(n) amortized.” Memorize it word-for-word.

Curated LeetCode Practice List

The classic monotonic-stack problem set, grouped by difficulty.

Easy

#ProblemPattern Variant
496Next Greater Element IDecreasing stack + map
1475Final Prices With a Special DiscountNext smaller-or-equal element
1063Number of Valid SubarraysPrevious smaller (premium)

Medium

#ProblemPattern Variant
739Daily TemperaturesDecreasing stack of indices
503Next Greater Element IICircular array (iterate 2n)
901Online Stock SpanStreaming monotonic decreasing
856Score of ParenthesesStack arithmetic on nesting
1019Next Greater Node in Linked ListSame pattern, linked-list input
402Remove K DigitsGreedy increasing-stack pop
316Remove Duplicate LettersIncreasing stack + lookahead
1856Maximum Subarray Min-ProductSpan-as-minimum + prefix sum

Hard

#ProblemPattern Variant
84Largest Rectangle in HistogramIncreasing stack + width formula
85Maximal RectanglePer-row histogram
42Trapping Rain WaterDecreasing stack horizontal layers
907Sum of Subarray MinimumsContribution + two passes
2104Sum of Subarray RangesMax-sum minus min-sum
1944Number of Visible People in a QueueMonotonic + counting
975Odd Even JumpTwo monotonic-stack passes + DP

Classic Problems

ProblemStack TypeKey Insight
Next GreaterDecreasingPop smaller, push current
Previous SmallerIncreasingPop larger or equal
HistogramIncreasingCalculate area on pop
Daily TemperaturesDecreasingStore indices, compute diff
Stock SpanDecreasingCount days with smaller price
Sum of Subarray MinimumsIncreasingContribution technique — each element’s span

Practice Problems

Next Greater Element I

Basic monotonic stack

Largest Rectangle

Classic histogram problem

Trapping Rain Water

Multi-approach problem

Sum of Subarray Mins

Contribution technique

Interview Questions

1. Explain the monotonic stack pattern. Why does it reduce the “next greater element” problem from O(n^2) to O(n)?

What interviewers are really testing: Whether you understand the amortized argument — that each element is pushed and popped at most once — and can explain why the stack maintains its invariant. The O(n) claim without the proof is not enough. Strong Answer:
  • A monotonic stack maintains elements in sorted order (increasing or decreasing). For “next greater element,” we use a decreasing stack (bottom to top). We iterate through the array; for each new element, we pop all stack elements smaller than it — each popped element just found its next greater element (the current element).
  • The O(n) proof: every element is pushed exactly once and popped at most once. Across the entire loop, we do at most n pushes and at most n pops, giving 2n total operations — O(n). The while loop inside the for loop is misleading; it looks nested, but the total work across all iterations is bounded by n pops.
  • Think of it like a bouncer at a club with a height rule. When a tall person arrives, the bouncer removes everyone shorter in line. Each person enters and leaves the line at most once.
  • After processing the entire array, any elements remaining on the stack have no next greater element — their answer is -1 (or whatever the default is).
Red flag answer: “We use a stack to store elements and check them.” This is too vague. Another red flag is saying the time complexity is O(n^2) because of the nested while loop — this shows a failure to apply amortized analysis. Follow-ups:
  1. How would you modify this to find the “next greater or equal” element instead of “strictly greater”?
  2. If the array is circular (the element after the last wraps to the first), how do you adapt the monotonic stack approach?

2. When do you use an increasing vs. decreasing monotonic stack? Give the decision rule.

What interviewers are really testing: Whether you have internalized the pattern rather than memorizing individual problems. The ability to choose the correct stack direction from a problem statement is the skill that transfers across problems. Strong Answer:
  • The rule is: the stack direction is opposite to what you are searching for. Looking for the “next greater”? You need elements waiting on the stack to be in decreasing order, so that a larger arriving element triggers resolution. Looking for the “next smaller”? You need increasing order, so that a smaller element triggers resolution.
  • For “next” problems (looking rightward), the answer is found on pop — the popped element found its answer when a new element violated the stack’s ordering. For “previous” problems (looking leftward), the answer is found on push — whatever remains on the stack top after popping is the nearest element in that direction.
  • Concrete mapping: Next Greater = decreasing stack, pop when current is greater than top. Next Smaller = increasing stack, pop when current is less than top. Previous Greater = decreasing stack, answer is stack top before pushing. Previous Smaller = increasing stack, answer is stack top before pushing.
  • Memory trick I use: “Next resolves on pop, Previous resolves on push.” This one-liner covers all four variants.
Red flag answer: “I just try both directions and see which works.” This wastes interview time and signals a lack of systematic understanding. Another red flag is confusing “increasing” and “decreasing” — always clarify whether you mean bottom-to-top or the opposite. Follow-ups:
  1. For the “Sum of Subarray Minimums” problem, you need both the previous smaller and next smaller element for each position. How do you combine two monotonic stack passes?
  2. What happens with duplicate values? Does “strictly less than” vs “less than or equal” in the pop condition change which stack direction you use?

3. Walk me through how the “Largest Rectangle in Histogram” problem works with a monotonic stack.

What interviewers are really testing: This is the canonical hard monotonic stack problem. They want to see if you can explain the left/right boundary logic, the area calculation on pop, and how the start index tracks how far each bar extends backward. Strong Answer:
  • The core idea: for each bar, find how far it can extend left and right as the shortest bar. The rectangle area for that bar is height * (right_boundary - left_boundary). A monotonic increasing stack (by height) lets us compute both boundaries efficiently.
  • We maintain a stack of (start_index, height) pairs in increasing order of height. When we encounter a bar shorter than the stack top, we pop — the popped bar can no longer extend right (the current bar blocks it). Its area is height * (current_index - start_index). Crucially, the current shorter bar can extend back to where the popped bar started, so we track start = popped.start_index.
  • After processing all bars, anything left on the stack extends all the way to the right edge of the histogram. For each remaining (start, height), the area is height * (n - start).
  • Example: heights [2, 1, 5, 6, 2, 3]. The largest rectangle is height 2, spanning indices 0-5, giving area 26 = 12. Wait — actually it is height 5 spanning indices 2-3, area 10? Let me recheck: height 2 from index 0 to 5 gives 26=12. But the bar at index 1 has height 1, which blocks the height-2 bar. The actual answer is 10 (height 5 across indices 2-3). This kind of careful walkthrough is what interviewers want to see.
Red flag answer: “I would check every pair of bars and compute the rectangle.” That is O(n^2). A more subtle red flag is getting the area formula wrong — forgetting that the start index carries backward from popped elements, not from the current index. Follow-ups:
  1. How do you extend this to “Maximal Rectangle” in a 2D binary matrix?
  2. Could you solve this problem with a divide-and-conquer approach? What would the complexity be?

4. Solve “Daily Temperatures” — for each day, find how many days until a warmer temperature. How is this different from “Next Greater Element”?

What interviewers are really testing: Whether you can recognize that this is a direct application of the monotonic stack “next greater” pattern and adapt it to return distances instead of values. Simple but tells the interviewer if you pattern-match effectively. Strong Answer:
  • This is exactly the “Next Greater Element” pattern, but instead of recording the value of the next greater element, we record the distance (index difference). The stack holds indices of days still waiting for a warmer day.
  • Iterate through temperatures. For each day i, while the stack is non-empty and temperatures[i] > temperatures[stack.top()], pop the top index idx and set result[idx] = i - idx. Then push i.
  • Days remaining on the stack after the loop never find a warmer day, so their result stays 0 (the default).
  • The structural difference from the basic “Next Greater Element” is purely in what we record: result[idx] = nums[i] (value) vs. result[idx] = i - idx (distance). The stack logic is identical. Recognizing this mapping is the skill — once you see it, the problem is a 5-minute solve.
Red flag answer: “For each day, scan forward until I find a warmer day.” That is O(n^2). Also, implementing the solution but not being able to articulate the connection to the “Next Greater Element” pattern suggests rote memorization rather than understanding. Follow-ups:
  1. What if we also need to find how many days until a cooler temperature? How does the stack direction change?
  2. Can you solve this problem in O(n) time with O(1) extra space (not counting the output array)? Hint: traverse from right to left.

5. Explain how the stack-based approach for “Trapping Rain Water” works. How does it differ from the two-pointer approach?

What interviewers are really testing: Whether you can explain two fundamentally different approaches to the same problem and compare their trade-offs. Being able to discuss multiple solutions and their mental models is a senior-level signal. Strong Answer:
  • The stack approach processes water horizontally, layer by layer. We maintain a decreasing stack of bar indices. When we encounter a bar taller than the stack top, water is trapped between the current bar (right wall) and the bar below the popped element (left wall). The water volume for that layer is width * bounded_height, where width = i - stack.top() - 1 and bounded_height = min(height[i], height[stack.top()]) - height[popped].
  • The two-pointer approach processes water vertically, column by column. Two pointers start at both ends, and we track left_max and right_max. At each step, move the pointer with the smaller max inward. The water above the current bar is max_on_its_side - height[current].
  • Trade-off: the stack approach uses O(n) space and is harder to implement correctly (the three-element interaction of current, popped, and stack top is error-prone). The two-pointer approach uses O(1) space and is conceptually elegant. Both are O(n) time.
  • There is also a third approach: build left_max[] and right_max[] prefix/suffix arrays, then for each bar, water = min(left_max[i], right_max[i]) - height[i]. This is O(n) time and O(n) space — easiest to understand, moderate space.
Red flag answer: Only knowing one approach. Interviewers frequently follow up with “can you do it differently?” If you only know the stack version, you will struggle. Another red flag is getting the water calculation wrong — forgetting to subtract height[popped] for the valley floor. Follow-ups:
  1. Why does the two-pointer approach move the pointer with the smaller max? What breaks if you move the other one?
  2. Can this problem be extended to 3D (trapping water on a 2D elevation map)? What data structure would you use?

6. What is the “contribution technique” used in “Sum of Subarray Minimums”? How does a monotonic stack enable it?

What interviewers are really testing: This is a staff-level question that tests whether you can think about each element’s individual contribution to the total rather than iterating over all subarrays. The monotonic stack is the tool, but the insight is the contribution decomposition. Strong Answer:
  • Instead of finding the minimum of every subarray (O(n^2) subarrays), we flip the question: for each element nums[i], in how many subarrays is it the minimum? Its contribution to the total sum is nums[i] * count_of_subarrays_where_it_is_minimum.
  • To count those subarrays, we need to find how far left and right nums[i] extends as the minimum. Let left[i] = distance to the previous smaller element, and right[i] = distance to the next smaller (or equal) element. The number of subarrays where nums[i] is the minimum is left[i] * right[i].
  • We use two monotonic stack passes: one left-to-right for previous smaller (increasing stack), one right-to-left for next smaller or equal (increasing stack). The “or equal” on one side prevents double-counting when duplicate values exist.
  • Total sum = sum of nums[i] * left[i] * right[i] for all i. Time is O(n), space is O(n). This technique generalizes to “Sum of Subarray Maximums,” “Sum of Subarray Ranges,” and similar problems.
Red flag answer: “Iterate over all subarrays and find the minimum of each.” That is O(n^2) or O(n^3). A more subtle red flag is getting the duplicate handling wrong — using strict less-than on both sides causes double-counting, and the candidate should explain why one side needs “less than or equal.” Follow-ups:
  1. Why does one boundary use strict less-than and the other uses less-than-or-equal? What happens if you use strict less-than on both?
  2. How would you adapt this technique to compute “Sum of Subarray Ranges” (max - min for every subarray)?

7. You have a circular array and need to find the next greater element for each position, where the search wraps around. How do you handle this?

What interviewers are really testing: Whether you can extend the linear monotonic stack to handle circular arrays. The standard trick is conceptually simple but tests whether you have seen enough variations to know it. Strong Answer:
  • The trick is to iterate through the array twice (indices 0 to 2n-1) and use i % n to get the actual index. This simulates the circular wrap-around. In the first pass, elements find their next greater if it exists to the right. In the second pass, elements that wrap around find their next greater from the beginning of the array.
  • We only push indices during the first pass (when i is less than n) to avoid duplicating entries. During the second pass, we only pop and resolve — we do not push new indices because those are duplicates.
  • Actually, the cleaner implementation pushes i % n for all i in [0, 2n) and checks stack elements as usual, but only initializes the result array with -1 for size n. Each element can be pushed up to twice and popped up to twice, so total operations are O(n). The result array only stores answers for the original n positions.
  • Example: [2, 1, 2, 4, 3] — the element 3 at index 4 has no next greater in the linear case (-1), but in the circular case, wrapping around, its next greater is 4 at index 3.
Red flag answer: “Concatenate the array with itself and run the normal algorithm.” This works but wastes space by creating a 2n-length array. Using modular indexing avoids the copy. A worse red flag is not knowing the “iterate twice” trick at all. Follow-ups:
  1. Does the circular approach change the time complexity? What about space?
  2. Can you think of a problem where the circular monotonic stack variant is useful in a real system (not just a LeetCode exercise)?

Interview Deep-Dive

What interviewers are really testing: This is the “Largest Rectangle in Histogram” problem reframed. The interviewer wants to see if you recognize the pattern from a different problem statement and can articulate the monotonic stack invariant and the area calculation logic without prompting.Strong Answer:
  • This is exactly the histogram rectangle problem. For each bar, I need to find how far left and right it can extend as the shortest bar. The rectangle area for that bar is height[i] * (right_boundary - left_boundary).
  • I maintain a monotonic increasing stack (by height). When I encounter a bar shorter than the stack top, I pop. The popped bar’s right boundary is the current index (the first shorter bar to the right), and its left boundary is the new stack top (the first shorter bar to the left). The width is current_index - stack_top - 1.
  • The key insight people miss: when I pop a bar and compute its area, the current shorter bar can extend backward to where the popped bar started. So I track a start index that carries backward through successive pops. This means the current bar’s effective left boundary may be far to the left of its actual position.
  • After processing all bars, anything remaining on the stack extends to the right edge. For each remaining (start, height), the area is height * (n - start).
  • Time is O(n) because each bar is pushed and popped at most once. Space is O(n) for the stack.
Follow-up: How do you extend this to find the maximal rectangle in a 2D binary matrix?Follow-up Answer: The 2D extension is elegant: treat each row as the base of a histogram. For each row, compute the histogram heights by accumulating consecutive 1s vertically. If matrix[i][j] = 1, then heights[j] = heights[j] + 1 (building up from the previous row). If matrix[i][j] = 0, then heights[j] = 0 (the column is broken). Then run the 1D histogram algorithm on each row’s heights. The overall answer is the maximum across all rows. Total time is O(m * n) where m is rows and n is columns — you do O(n) histogram work for each of m rows. This is LeetCode 85 (Maximal Rectangle), and it is the canonical example of reducing a 2D problem to repeated applications of a 1D technique.
What interviewers are really testing: The contribution technique, which flips the perspective from “for each subarray, find its minimum” to “for each element, count how many subarrays it is the minimum of.” This is a staff-level pattern recognition test.Strong Answer:
  • The key insight is to compute each element’s “span” as the minimum. For element nums[i], find left[i] = the number of consecutive elements to the left that are greater than or equal to nums[i] (inclusive of i itself), and right[i] = the number of consecutive elements to the right that are strictly greater. The number of subarrays where nums[i] is the minimum is left[i] * right[i].
  • I use two monotonic stack passes: one left-to-right with an increasing stack to find the “previous smaller element” distance for each position, and one right-to-left with an increasing stack to find the “next smaller or equal element” distance.
  • The critical subtlety is handling duplicates. If I use strict less-than on both sides, I will double-count subarrays where two equal elements are both “the minimum.” The fix: use strict less-than on one side (say, left) and less-than-or-equal on the other (right). This assigns each subarray to exactly one element as its “responsible minimum.”
  • Total sum = sum(nums[i] * left[i] * right[i]) for all i. Time is O(n) for two passes, space is O(n) for the stack and boundary arrays.
Follow-up: How would you extend this to compute the “Sum of Subarray Ranges” — the sum of (max - min) for every subarray?Follow-up Answer: Decompose the problem: Sum of Ranges = Sum of Subarray Maximums - Sum of Subarray Minimums. You already know how to compute the sum of subarray minimums with the contribution technique. The sum of subarray maximums is the mirror: use a monotonic decreasing stack instead of increasing, find each element’s span as the maximum, and sum nums[i] * left_max[i] * right_max[i]. Both passes are O(n), so the total is O(n). This decomposition is the standard approach for LeetCode 2104 (Sum of Subarray Ranges).
What interviewers are really testing: Whether you can apply the monotonic stack pattern to a streaming/online problem rather than a static array. This also tests API design sense and amortized complexity reasoning.Strong Answer:
  • This is the “previous greater element” pattern adapted as a streaming API. I maintain a monotonic decreasing stack of (price, span) pairs. The stack invariant: prices are in decreasing order from bottom to top.
  • When a new price arrives, I initialize its span as 1. Then I pop all stack elements whose price is less than or equal to the new price, adding their spans to the current span. This is because if the new price is higher, it “absorbs” all those consecutive days. Then I push (new_price, accumulated_span) onto the stack.
  • Each element is pushed exactly once and popped at most once across all calls, so the amortized cost per next() call is O(1), even though a single call might pop many elements.
  • The API looks like:
    class StockSpanner:
        def __init__(self): self.stack = []
        def next(self, price):
            span = 1
            while self.stack and self.stack[-1][0] <= price:
                span += self.stack.pop()[1]
            self.stack.append((price, span))
            return span
    
  • This is O(1) amortized time per call and O(n) total space for n calls. In a real trading system, you might cap the stack size to limit memory, accepting that spans beyond a certain window are truncated.
Follow-up: What if the interviewer adds a twist: you also need to efficiently query “what is the maximum span seen so far” at any point?Follow-up Answer: Maintain a separate running maximum variable. Each time next() computes a span, update max_span = max(max_span, span). This adds O(1) work per call and O(1) space. The trick is that this is a monotonically non-decreasing value — once a large span is seen, it can only be equaled or exceeded, never reduced. If instead they asked for the maximum span within the last K days (a sliding window over spans), you would need a monotonic deque on the span values, which is the “Sliding Window Maximum” pattern applied to the stream of spans.
What interviewers are really testing: Breadth of problem-solving toolkit and the ability to compare approaches along multiple axes — not just time complexity, but code complexity, conceptual clarity, and how well each approach extends to variants.Strong Answer:
  • Prefix arrays (left_max / right_max): Build left_max[i] = max height from index 0 to i, and right_max[i] = max height from i to n-1. Water at each index is min(left_max[i], right_max[i]) - height[i]. Time O(n), space O(n). This is the easiest to understand and implement. I would pick this if I want a quick, bug-free solution and do not care about O(n) space.
  • Two pointers: Two pointers start at both ends, tracking left_max and right_max. Move the pointer with the smaller max inward. Water at the current position is max_on_its_side - height[current]. Time O(n), space O(1). This is the most elegant and space-efficient. I would pick this if the interviewer cares about space optimization or if I want to impress with a clean single-pass solution.
  • Monotonic stack: Maintain a decreasing stack of indices. When a taller bar arrives, pop and compute water layer by layer (horizontally). Time O(n), space O(n) for the stack. This is the hardest to get right — the three-element interaction (current bar, popped bar, new stack top) is error-prone. I would pick this only if the interviewer specifically asks for a stack-based solution, or if the problem is a variant where the stack approach generalizes better (like computing water in a 3D terrain using a priority queue extension).
  • My recommendation in a real interview: Start with the prefix array approach to show understanding. Then mention the two-pointer optimization to show you can reduce space. If time remains, mention the stack approach as an alternative mental model. This demonstrates breadth without risking a buggy implementation.
Follow-up: The 3D version of this problem — “Trapping Rain Water II” — takes a 2D elevation map. Which of these three approaches generalizes, and what data structure replaces the stack or pointers?Follow-up Answer: The two-pointer approach generalizes via a min-heap (priority queue). Start by pushing all border cells into the min-heap. Process the lowest border cell first — water above any interior neighbor is bounded by this lowest wall. For each unvisited neighbor, the water it holds is max(0, current_wall_height - neighbor_height). Push the neighbor into the heap with max(current_wall_height, neighbor_height) as its effective wall height. This is a BFS-like expansion from the boundary inward, always processing the lowest wall first. Time is O(mn log(mn)), space is O(mn). The prefix array and stack approaches do not generalize cleanly to 2D because the water at each cell is bounded by walls from all directions, not just left and right.

Interview Q&A Rapid-Fire (Rich Format)

The four questions below are FAANG-favorite monotonic-stack problems. Each one includes a strong answer framework, a real LeetCode example with full code, three senior follow-ups in <Note> blocks, common wrong answers, and further reading.
Strong Answer Framework:
  1. Recognize the pattern. “This is the ‘next greater element’ template — I am looking for the first day to my right with a higher temperature.”
  2. Reject the brute force. O(n^2) — for each day scan forward. Times out for inputs over 10000.
  3. Introduce the monotonic decreasing stack of indices. Days waiting for a warmer future sit on the stack in non-increasing order of temperature.
  4. Walk through the resolve-on-pop step. When today is warmer than the day on top, pop and record today_index - popped_index.
  5. Prove the O(n) amortized cost. Each index pushed once, popped at most once — 2n operations total.
Real LeetCode Example — LC 739: Daily Temperatures
def daily_temperatures(temps):
    n = len(temps)
    result = [0] * n
    stack = []                            # indices of waiting days, decreasing temps
    for i, t in enumerate(temps):
        while stack and temps[stack[-1]] < t:
            j = stack.pop()
            result[j] = i - j             # day-distance, not temperature value
        stack.append(i)
    return result
Time O(n), space O(n) for the stack.
Senior Follow-up 1: “Why store indices and not temperatures?” — Without indices we cannot compute i - j, the day distance. The values themselves are recoverable via temps[stack[-1]]. Default to indices in every monotonic-stack problem; it is the lossless choice.
Senior Follow-up 2: “Solve in O(1) extra space, not counting the output.” — Traverse right-to-left. For each day i, jump forward using already-computed answers: if result[i + 1] says “wait k days,” and day i + 1 + k is still not warmer than i, jump again using result[i + 1 + k]. Each jump skips a known-cold range. Total work is amortized O(n).
Senior Follow-up 3: “Now make it streaming — temperatures arrive one per day.” — This is LC 901 (Online Stock Span). Maintain a stack of (price, span) pairs. On a new price, accumulate the spans of all popped smaller-or-equal prices. Amortized O(1) per call.
Common Wrong Answers:
  • “Brute force forward scan” — O(n^2), fails on large inputs.
  • “Push values instead of indices” — cannot compute the distance.
  • “Use a monotonic increasing stack” — finds next smaller day, not warmer day. Direction matters.
Further Reading:
Strong Answer Framework:
  1. Mention all three approaches. Prefix arrays (O(n) space, easiest), two-pointer (O(1) space, most elegant), monotonic stack (O(n) space, processes water in horizontal layers). Discussing breadth is the senior signal.
  2. Code the two-pointer first if asked for the cleanest solution.
  3. Pivot to the stack version if asked for an alternative or for the streaming case.
  4. Explain the two-pointer invariant. “If left_max <= right_max, water at the left position is bounded by left_max. The pointer with the smaller max can safely commit and move.”
  5. Explain the stack version. “When a taller bar arrives, water is trapped between the current bar (right wall), the new stack top after popping (left wall), and the popped element (valley floor).”
Real LeetCode Example — LC 42: Trapping Rain Water (both approaches)
# Approach 1: Two pointers -- O(n) time, O(1) space
def trap_two_pointer(height):
    left, right = 0, len(height) - 1
    left_max = right_max = water = 0
    while left < right:
        if height[left] < height[right]:
            if height[left] >= left_max:
                left_max = height[left]
            else:
                water += left_max - height[left]
            left += 1
        else:
            if height[right] >= right_max:
                right_max = height[right]
            else:
                water += right_max - height[right]
            right -= 1
    return water

# Approach 2: Monotonic decreasing stack -- O(n) time, O(n) space, layer-by-layer
def trap_stack(height):
    stack = []
    water = 0
    for i, h in enumerate(height):
        while stack and h > height[stack[-1]]:
            bottom = stack.pop()
            if not stack: break           # no left wall -> water escapes
            width = i - stack[-1] - 1
            bounded = min(h, height[stack[-1]]) - height[bottom]
            water += width * bounded
        stack.append(i)
    return water
Senior Follow-up 1: “Why does the two-pointer move the pointer with the smaller max?” — Because the water at that side is guaranteed to be bounded by its own max. Even if the other side has an unknown taller wall ahead, it cannot reduce the water below min(left_max, right_max) = left_max (when left is smaller). So we can commit the water amount and advance.
Senior Follow-up 2: “Extend to 2D — LC 407 Trapping Rain Water II.” — The two-pointer idea generalizes to a min-heap (Dijkstra-like) sweep from the boundary inward. Push every border cell into the heap. Pop the lowest, expand to unvisited neighbors — the water held at each neighbor is max(0, current_wall - neighbor_height). Push neighbor with max(current_wall, neighbor_height) as its effective wall.
Senior Follow-up 3: “Compare the stack and two-pointer approaches qualitatively.” — The stack processes water in horizontal layers (rectangles between left and right walls); two-pointer processes water in vertical columns (one above each position). Both arrive at the same total but via different decompositions — a beautiful demonstration of how data structure choice changes the mental model.
Common Wrong Answers:
  • Knowing only one approach. Interviewers love to ask “can you do it differently?” — if you only know the stack version, you will struggle.
  • In the stack version, width = i - popped_index instead of i - stack[-1] - 1. Off-by-one bug.
  • In the two-pointer version, moving the wrong pointer when maxes are equal — the algorithm still works, but careless reasoning about why is a red flag.
Further Reading:
Strong Answer Framework:
  1. State the goal. “For each bar, find how far left and right it can extend as the shortest bar in the rectangle. Area = height * (right_boundary - left_boundary).”
  2. Maintain a monotonic increasing stack of indices.
  3. Resolve on pop. When a shorter bar arrives, the popped bar’s right boundary is the current index, and its left boundary is the new stack top after popping (or -1 if empty).
  4. The width formula. width = i - stack[-1] - 1 (or i if stack is empty). Not i - popped.
  5. Use a sentinel. Append 0 to the heights array to force a final flush.
Real LeetCode Example — LC 84: Largest Rectangle in Histogram
def largest_rectangle_area(heights):
    stack = []
    max_area = 0
    heights = heights + [0]               # sentinel forces flush
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            top = stack.pop()
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, heights[top] * width)
        stack.append(i)
    return max_area
Time O(n), space O(n).
Senior Follow-up 1: “Why is width i - stack[-1] - 1 and not i - top?” — Because previously-popped bars between the new stack top and i were ALL taller than the popped bar. So the popped bar’s rectangle of its own height extends through all of them. Get this wrong and you systematically under-count area.
Senior Follow-up 2: “Extend to 2D — LC 85 Maximal Rectangle in a binary matrix.” — Treat each row as the base of a histogram. Bar height at column j is the count of consecutive 1s ending at row i. Run the 1D histogram algorithm on each row. O(rows * cols) total.
Senior Follow-up 3: “Without the sentinel trick, what does the cleanup look like?” — After the main loop, while the stack is non-empty, pop and compute area with width = n - stack[-1] - 1 (or n if stack now empty). Same logic, just written twice. Sentinel is cleaner; cleanup loop is more explicit. Both work.
Common Wrong Answers:
  • “Width is i - popped_index.” Wrong by missing the previously-popped contributions.
  • “Use a monotonic decreasing stack.” Wrong direction — pops would happen on smaller arrivals, not larger.
  • “Brute force every (i, j) pair.” O(n^2) with min-finding inside; O(n^3) naive. Fails the constraint.
Further Reading:
Strong Answer Framework:
  1. Flip the perspective. Instead of “for each subarray find its min”, ask “for each element, in how many subarrays is it the min?”
  2. Compute span boundaries. For element nums[i], find left[i] = distance to nearest strictly smaller on the left, and right[i] = distance to nearest smaller-or-equal on the right. The number of subarrays where nums[i] is the min is left[i] * right[i].
  3. Two monotonic stack passes. One left-to-right (increasing stack) for previous-smaller, one right-to-left for next-smaller-or-equal.
  4. Justify the asymmetric strict/non-strict. With duplicates, using strict < on both sides causes double-counting subarrays where two equal elements both qualify. Make one side non-strict to break the tie deterministically.
  5. Sum it up. Total = sum of nums[i] * left[i] * right[i]. Time O(n), space O(n).
Real LeetCode Example — LC 907: Sum of Subarray Minimums
def sum_subarray_mins(arr):
    MOD = 10**9 + 7
    n = len(arr)
    # left[i] = distance to previous STRICTLY smaller (or i+1 if none)
    # right[i] = distance to next SMALLER-OR-EQUAL (or n-i if none)
    left = [0] * n
    right = [0] * n
    stack = []
    for i in range(n):
        while stack and arr[stack[-1]] >= arr[i]:    # >= to be strict on left
            stack.pop()
        left[i] = i - stack[-1] if stack else i + 1
        stack.append(i)
    stack = []
    for i in range(n - 1, -1, -1):
        while stack and arr[stack[-1]] > arr[i]:     # > to be non-strict on right
            stack.pop()
        right[i] = stack[-1] - i if stack else n - i
        stack.append(i)
    return sum(arr[i] * left[i] * right[i] for i in range(n)) % MOD
Time O(n), space O(n).
Senior Follow-up 1: “Why is one boundary strict and the other not?” — Suppose arr = [1, 2, 1]. Without the asymmetry, the subarray [1, 2, 1] is counted twice (once for each 1 as the min). The asymmetry assigns each subarray to exactly one “responsible minimum” — typically the leftmost or rightmost occurrence.
Senior Follow-up 2: “Extend to Sum of Subarray Ranges (max - min over every subarray).” — LC 2104. Compute Sum of Subarray Maxes minus Sum of Subarray Mins. The maxes use a decreasing stack with the same contribution technique. Both sums in O(n) — total O(n) for the whole problem.
Senior Follow-up 3: “Why does the contribution technique reduce O(n^2) subarrays to O(n) work?” — We do not enumerate subarrays at all. Each element contributes a closed-form count of “subarrays where I am the min” derivable from its left and right spans. The spans are computed once each in O(n) via monotonic stacks. The trick is replacing enumeration with span arithmetic.
Common Wrong Answers:
  • “Iterate every subarray and sum the min” — O(n^2) at minimum, often O(n^3); times out.
  • “Use strict inequality on both sides” — double-counts subarrays with duplicate minimums.
  • “Forget the modulo” — LC 907 specifically requires % (10^9 + 7).
Further Reading: