Monotonic Stack

Hashing, Stacks & QueuesPriority 4 of 5 — High value — a gap here costs you roundsHigh value Updated Sep 18, 2026
Section priorityPriority 5 of 5 — Must know — expect it in almost every loopMust knowPriority 4 of 5 — High value — a gap here costs you roundsHigh valuePriority 3 of 5 — Worth knowing — usually a variant of a must-know patternWorth knowingPriority 2 of 5 — Niche — read once, revisit only if a company is known to askNicheMarked on the sections that carry it — unmarked sections are background/reference.

Scope — Next greater / previous smaller / span / histogram problems — the stack stays sorted so each element is pushed and popped once. See also: stack.md — plain LIFO problems; monotonic_queue.md — the sliding-window counterpart; heap.md — when you need a global extreme instead of a neighbouring one; greedy.md — where the append-only “stacks” of §2-10 really belong.

LeetCode Problem Lists

Overview

Monotonic Stack is a specialized stack data structure that maintains a monotonic (either strictly increasing or strictly decreasing) order of elements. It efficiently solves problems related to finding next greater/smaller elements, histogram areas, and sequence optimization problems.

Key Properties

  • Time Complexity: O(n) for most operations (each element pushed/popped once)
  • Space Complexity: O(n) for the stack storage
  • Core Idea: Maintain monotonic order while processing elements sequentially
  • When to Use: Finding next/previous greater/smaller elements, histogram problems, sequence optimization

References

Problem Categories

Pattern 1: Next/Previous Greater Element — LC 739

  • Description: Find the next or previous element that is greater than current element
  • Examples: LC 496 (Next Greater Element I), LC 503 (Next Greater Element II), LC 739 (Daily Temperatures)
  • Pattern: Use decreasing monotonic stack, pop when finding greater element

Pattern 2: Next/Previous Smaller Element — LC 84

  • Description: Find the next or previous element that is smaller than current element
  • Examples: LC 84 (Largest Rectangle), LC 42 (Trapping Rain Water), LC 907 (Sum of Subarray Minimums)
  • Pattern: Use increasing monotonic stack, pop when finding smaller element

Pattern 3: Histogram and Area Problems — LC 84

  • Description: Calculate areas, rectangles, or volumes using height information
  • Examples: LC 84 (Largest Rectangle in Histogram), LC 42 (Trapping Rain Water), LC 85 (Maximal Rectangle)
  • Pattern: Find boundaries using monotonic stack, calculate areas between boundaries

Pattern 4: Sequence Order and Validation — LC 456

  • Description: Validate sequences, find patterns, or maintain order constraints
  • Examples: LC 456 (132 Pattern), LC 901 (Online Stock Span), LC 1856 (Maximum Subarray Min-Product)
  • Pattern: Use stack to maintain sequence properties and validate patterns

Pattern 5: Optimization and Maximum/Minimum — LC 1793

  • Description: Find optimal solutions involving maximum or minimum constraints
  • Examples: LC 1944 (Number of Visible People), LC 2104 (Sum of Subarray Ranges), LC 1793 (Maximum Score)
  • Pattern: Use monotonic properties to maintain optimal candidates

Pattern 6: Circular Arrays — LC 503

  • Description: Handle circular or cyclic array problems
  • Examples: LC 503 (Next Greater Element II), LC 457 (Circular Array Loop)
  • Pattern: Process array twice or use modular arithmetic with monotonic stack

Templates & Algorithms

Template Comparison Table

Template Type Use Case Stack Order When to Use
Decreasing Stack Next/Previous Greater Decreasing Find elements greater than current
Increasing Stack Next/Previous Smaller Increasing Find elements smaller than current
Histogram Area Rectangle/Area Problems Increasing Calculate areas using heights
Circular Array Cyclic Problems Varies Process circular sequences
Pattern Validation Sequence Validation Varies Validate specific patterns
Optimization Stack Max/Min Problems Varies Maintain optimal candidates

Universal Template

python
def monotonic_stack_template(arr):
    """
    Universal template for monotonic stack problems
    Modify the condition and processing logic based on problem requirements
    """
    stack = []  # Store indices or values
    result = []
    
    for i, val in enumerate(arr):
        # Pop elements that violate monotonic property
        while stack and should_pop(stack, val, i):
            # Process the popped element
            popped = stack.pop()
            process_popped_element(popped, i, result)
        
        # Add current element to stack
        stack.append(i)  # or val depending on problem
    
    # Process remaining elements in stack
    while stack:
        popped = stack.pop()
        process_remaining_element(popped, result)
    
    return result

def should_pop(stack, current_val, current_idx):
    """Define when to pop based on problem requirements"""
    # For next greater: return arr[stack[-1]] <= current_val
    # For next smaller: return arr[stack[-1]] >= current_val
    pass

def process_popped_element(popped_idx, current_idx, result):
    """Process element when it's popped (found its next greater/smaller)"""
    pass

def process_remaining_element(popped_idx, result):
    """Process elements remaining in stack at the end"""
    pass
java
// Java Universal Template
public int[] monotonicStackTemplate(int[] arr) {
    Stack<Integer> stack = new Stack<>();
    int[] result = new int[arr.length];
    
    for (int i = 0; i < arr.length; i++) {
        // Pop elements that violate monotonic property
        while (!stack.isEmpty() && shouldPop(stack, arr, i)) {
            int poppedIdx = stack.pop();
            processElement(poppedIdx, i, result, arr);
        }
        
        // Add current element to stack
        stack.push(i);
    }
    
    // Process remaining elements
    while (!stack.isEmpty()) {
        int poppedIdx = stack.pop();
        processRemainingElement(poppedIdx, result);
    }
    
    return result;
}

private boolean shouldPop(Stack<Integer> stack, int[] arr, int currentIdx) {
    // Define condition based on problem requirements
    return arr[stack.peek()] <= arr[currentIdx]; // For next greater
}

Template 1: Next Greater Element (Decreasing Stack) — LC 496

python
def next_greater_element(nums):
    """
    Find next greater element for each element
    LC 496, LC 503, LC 739
    """
    n = len(nums)
    result = [-1] * n
    stack = []  # Store indices
    
    for i in range(n):
        # Pop smaller or equal elements
        while stack and nums[stack[-1]] < nums[i]:
            idx = stack.pop()
            result[idx] = nums[i]  # Found next greater
        
        stack.append(i)
    
    return result
java
// Java Template 1
public int[] nextGreaterElement(int[] nums) {
    int n = nums.length;
    int[] result = new int[n];
    Arrays.fill(result, -1);
    Stack<Integer> stack = new Stack<>();
    
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
            result[stack.pop()] = nums[i];
        }
        stack.push(i);
    }
    
    return result;
}

Template 2: Next Smaller Element (Increasing Stack) — LC 84

python
def next_smaller_element(nums):
    """
    Find next smaller element for each element
    Used in LC 84, LC 42
    """
    n = len(nums)
    result = [-1] * n
    stack = []  # Store indices
    
    for i in range(n):
        # Pop greater or equal elements
        while stack and nums[stack[-1]] > nums[i]:
            idx = stack.pop()
            result[idx] = nums[i]  # Found next smaller
        
        stack.append(i)
    
    return result

Template 3: Largest Rectangle in Histogram — LC 84

python
def largest_rectangle_area(heights):
    """
    Find largest rectangle area in histogram
    LC 84, LC 85
    """
    stack = []  # Store indices
    max_area = 0
    heights.append(0)  # Add sentinel
    
    for i, h in enumerate(heights):
        # Pop taller bars and calculate area
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, height * width)
        
        stack.append(i)
    
    return max_area
java
// Java Template 3
public int largestRectangleArea(int[] heights) {
    Stack<Integer> stack = new Stack<>();
    int maxArea = 0;
    int n = heights.length;
    
    for (int i = 0; i <= n; i++) {
        int h = (i == n) ? 0 : heights[i];
        
        while (!stack.isEmpty() && heights[stack.peek()] > h) {
            int height = heights[stack.pop()];
            int width = stack.isEmpty() ? i : i - stack.peek() - 1;
            maxArea = Math.max(maxArea, height * width);
        }
        
        stack.push(i);
    }
    
    return maxArea;
}

Template 4: Circular Array Processing — LC 503

python
def next_greater_circular(nums):
    """
    Find next greater element in circular array
    LC 503
    """
    n = len(nums)
    result = [-1] * n
    stack = []
    
    # Process array twice to handle circular nature
    for i in range(2 * n):
        # Pop smaller elements
        while stack and nums[stack[-1]] < nums[i % n]:
            idx = stack.pop()
            result[idx] = nums[i % n]
        
        # Only add indices from first pass
        if i < n:
            stack.append(i)
    
    return result

Template 5: Stack with Additional Information

python
def monotonic_stack_with_info(nums):
    """
    Store additional information with stack elements
    Used for complex calculations
    """
    stack = []  # Store (index, value, additional_info)
    result = []
    
    for i, val in enumerate(nums):
        while stack and stack[-1][1] <= val:
            idx, old_val, info = stack.pop()
            # Process with additional information
            result.append(calculate_result(idx, i, old_val, val, info))
        
        # Calculate additional information for current element
        additional_info = calculate_info(val, stack)
        stack.append((i, val, additional_info))
    
    return result

Template 6: Pattern Validation (132 Pattern) — LC 456

python
def find_132_pattern(nums):
    """
    Find 132 pattern in array
    LC 456
    """
    n = len(nums)
    if n < 3:
        return False
    
    stack = []  # Store potential k values (decreasing)
    second = float('-inf')  # The "2" in 132 pattern
    
    # Traverse from right to left
    for i in range(n - 1, -1, -1):
        if nums[i] < second:  # Found "1" < "2"
            return True
        
        # Pop smaller values and update second
        while stack and stack[-1] < nums[i]:
            second = stack.pop()
        
        stack.append(nums[i])
    
    return False

Problems by Pattern

Pattern-Based Problem Classification

Pattern 1: Next/Previous Greater Element Problems

Problem LC # Key Technique Difficulty Template
Next Greater Element I 496 Decreasing stack Easy Template 1
Next Greater Element II 503 Circular array Medium Template 4
Daily Temperatures 739 Distance calculation Medium Template 1
Remove K Digits 402 Greedy + stack Medium Template 1
Remove Duplicate Letters 316 Lexicographical + stack Medium Template 1
Sliding Window Maximum 239 Monotonic deque Hard Template 1
Shortest Unsorted Array 581 Two-pass stack Medium Template 1
Sum of Subarray Ranges 2104 Next greater + smaller Medium Template 1+2

Pattern 2: Next/Previous Smaller Element Problems

Problem LC # Key Technique Difficulty Template
Largest Rectangle in Histogram 84 Area calculation Hard Template 3
Maximal Rectangle 85 2D histogram Hard Template 3
Sum of Subarray Minimums 907 Contribution method Medium Template 2
Number of Valid Subarrays 1063 Smaller element count Medium Template 2
Minimum Cost Tree From Leaf Values 1130 Optimal merging Medium Template 2
Find the Most Competitive Subsequence 1673 Subsequence selection Medium Template 2
Maximum Subarray Min-Product 1856 Min value as pivot Medium Template 2

Pattern 3: Histogram and Area Problems

Problem LC # Key Technique Difficulty Template
Trapping Rain Water 42 Water level calculation Hard Template 2
Container With Most Water 11 Two pointers alternative Medium Template 2
Maximal Rectangle 85 Row-wise histogram Hard Template 3
Maximum Rectangle 221 DP + histogram Medium Template 3
Minimum Number of Taps 1326 Interval coverage Hard Template 2
Constrained Subsequence Sum 1425 DP + monotonic deque Hard Template 2

Pattern 4: Sequence Order and Validation Problems

Problem LC # Key Technique Difficulty Template
132 Pattern 456 Pattern detection Medium Template 6
Online Stock Span 901 Monotonic stack Medium Template 1
Score of Parentheses 856 Nested structure Medium Template 5
Valid Parenthesis String 678 Balance validation Medium Template 5
Minimum Add to Make Parentheses Valid 921 Balance counting Medium Template 5
Validate Stack Sequences 946 Sequence simulation Medium Template 5
Maximum Nesting Depth of Parentheses 1614 Depth tracking Easy Template 5
Minimum Remove to Make Valid Parentheses 1249 Balance + removal Medium Template 5

Pattern 5: Optimization and Maximum/Minimum Problems

Problem LC # Key Technique Difficulty Template
Maximum Score of Good Subarray 1793 Two pointers + stack Hard Template 2
Number of Visible People in Queue 1944 Line of sight Medium Template 1
Car Fleet 853 Arrival-time running max — the stack never pops Medium §2-10
Count Robot Groups 4045 Same greedy, no finish line — compare speeds Medium §2-10
Car Fleet II 1776 Per-car collision time — a real stack, with pops Hard §2-10
Buildings With Ocean View 1762 Right-to-left scan Medium Template 1
Find the Winner of Circular Game 1823 Josephus problem Medium Template 4
Maximum Width Ramp 962 Index difference Medium Template 1
Steps to Make Array Non-decreasing 2289 Stack carries a dp value Medium Template 11
Pancake Sorting 969 Reverse operations Medium Template 1

Pattern 6: Circular Array Problems

Problem LC # Key Technique Difficulty Template
Next Greater Element II 503 Double array traversal Medium Template 4
Circular Array Loop 457 Cycle detection Medium Template 4
Design Circular Queue 622 Circular buffer Medium Template 4
Design Circular Deque 641 Double-ended circular Medium Template 4

Advanced/Mixed Pattern Problems

Problem LC # Key Technique Difficulty Template
Sum of Total Strength of Wizards 2281 Multiple stacks Hard Multiple
Number of Ways to Rearrange Sticks 1866 Combinatorics + stack Hard Template 5
Basic Calculator 224 Expression evaluation Hard Template 5
Basic Calculator II 227 Operator precedence Medium Template 5
Basic Calculator III 772 Full expression parsing Hard Template 5
Evaluate Reverse Polish Notation 150 Postfix evaluation Medium Template 5
Decode String 394 Nested decoding Medium Template 5
Find Duplicate Subtrees 652 Tree serialization Medium Template 5
Exclusive Time of Functions 636 Call stack simulation Medium Template 5
Minimum Window Subsequence 727 Two pointers + stack Hard Template 5

Problem Difficulty Distribution

  • Easy (8 problems): Basic next greater/smaller, simple validations
  • Medium (28 problems): Most common difficulty, various patterns
  • Hard (16 problems): Complex area calculations, advanced optimizations

Template Usage Frequency

  • Template 1 (Next Greater): 15 problems
  • Template 2 (Next Smaller): 12 problems
  • Template 3 (Histogram): 8 problems
  • Template 4 (Circular): 6 problems
  • Template 5 (Validation/Complex): 11 problems
  • Multiple Templates: 8 problems

Pattern Selection Strategy

Decision Framework Flowchart

text
Problem Analysis for Monotonic Stack:

1. Does the problem involve finding next/previous elements?
   ├── YES: Next/Previous GREATER elements?
   │   ├── YES: Use Template 1 (Decreasing Stack)
   │   │   ├── Array is circular? → Use Template 4 (Circular)
   │   │   └── Standard case → Template 1
   │   └── NO: Next/Previous SMALLER elements?
   │       ├── YES: Use Template 2 (Increasing Stack)
   │       └── NO: Continue to step 2
   └── NO: Continue to step 2

2. Does the problem involve heights/areas/rectangles?
   ├── YES: Rectangle area calculation?
   │   ├── YES: Use Template 3 (Histogram)
   │   └── NO: Water trapping/volume?
   │       └── YES: Use Template 2 (Next Smaller)
   └── NO: Continue to step 3

3. Does the problem involve sequence validation/patterns?
   ├── YES: Parentheses/brackets?
   │   ├── YES: Use Template 5 (Validation)
   │   └── NO: Specific pattern (like 132)?
   │       └── YES: Use Template 6 (Pattern Detection)
   └── NO: Continue to step 4

4. Does the problem involve optimization/max-min constraints?
   ├── YES: Multiple criteria optimization?
   │   ├── YES: Use Template 5 (Complex Info)
   │   └── NO: Simple max/min tracking?
   │       └── YES: Use Template 1 or 2
   └── NO: Continue to step 5

5. Does the problem involve circular arrays or cyclic behavior?
   ├── YES: Use Template 4 (Circular Processing)
   └── NO: Consider if monotonic stack is the right approach
       └── May need different data structure/algorithm

Step-by-Step Problem Analysis

  1. Identify the Core Requirement

    • Next/Previous element queries → Templates 1, 2, 4
    • Area/Rectangle calculations → Template 3
    • Pattern validation → Templates 5, 6
    • Optimization problems → Templates 1, 2, 5
  2. Determine Stack Order

    • Need greater elements → Decreasing stack (pop smaller)
    • Need smaller elements → Increasing stack (pop greater)
    • Area calculations → Usually increasing stack
    • Pattern detection → Varies by pattern
  3. Choose Processing Direction

    • Left to right: Most common, natural order
    • Right to left: For “next” elements, sometimes easier
    • Circular: Process array multiple times
  4. Decide What to Store

    • Indices: When need position information
    • Values: When only need element comparison
    • Tuples: When need additional information

Template Selection Quick Guide

Problem Type Template Stack Content Processing Order
Next Greater Template 1 Indices Left to Right
Next Smaller Template 2 Indices Left to Right
Previous Greater Template 1 Indices Left to Right
Previous Smaller Template 2 Indices Left to Right
Histogram Areas Template 3 Indices Left to Right
Circular Arrays Template 4 Indices 2x traversal
Pattern Detection Template 6 Values Right to Left
Complex Validation Template 5 Tuples Varies

Summary & Quick Reference

Complexity Quick Reference

Operation Time Space Notes
Push to Stack O(1) - Each element pushed once
Pop from Stack O(1) - Each element popped once
Overall Algorithm O(n) O(n) Amortized linear time
Next Greater/Smaller O(n) O(n) Single pass through array
Histogram Area O(n) O(n) Linear scan with stack
Circular Array O(n) O(n) Two passes, same complexity

Template Quick Reference

Template Pattern Key Code Pattern
Template 1 Next Greater while stack and nums[stack[-1]] < nums[i]
Template 2 Next Smaller while stack and nums[stack[-1]] > nums[i]
Template 3 Histogram while stack and heights[stack[-1]] > h
Template 4 Circular for i in range(2 * n)
Template 5 Validation Store additional info in stack
Template 6 Pattern Detection Right-to-left with condition tracking

Common Patterns & Tricks

Next Greater Element Pattern

python
# Standard next greater element
def next_greater_elements(nums):
    stack, result = [], [-1] * len(nums)
    for i, num in enumerate(nums):
        while stack and nums[stack[-1]] < num:
            result[stack.pop()] = num
        stack.append(i)
    return result

Contribution Method for Subarrays

python
# Count contribution of each element
def sum_subarray_mins(arr):
    n = len(arr)
    left = [-1] * n    # Previous smaller element
    right = [n] * n    # Next smaller element
    
    # Calculate left boundaries
    stack = []
    for i in range(n):
        while stack and arr[stack[-1]] >= arr[i]:
            stack.pop()
        left[i] = stack[-1] if stack else -1
        stack.append(i)
    
    # Calculate contribution
    result = 0
    for i in range(n):
        result += arr[i] * (i - left[i]) * (right[i] - i)
    return result % (10**9 + 7)

Histogram Area Calculation

python
# Largest rectangle with height as key
def largest_rectangle_area(heights):
    stack = []
    max_area = 0
    for i, h in enumerate(heights + [0]):
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            width = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, height * width)
        stack.append(i)
    return max_area

Problem-Solving Steps

  1. Step 1: Identify Pattern Type

    • Look for keywords: next/previous, greater/smaller, area, rectangle
    • Check for circular/cyclic requirements
    • Identify if validation or pattern detection is needed
  2. Step 2: Choose Appropriate Template

    • Use decision framework flowchart
    • Consider stack order (increasing vs decreasing)
    • Determine what information to store in stack
  3. Step 3: Implement Core Logic

    • Set up stack and result containers
    • Implement while loop with correct popping condition
    • Process popped elements appropriately
    • Handle remaining elements in stack
  4. Step 4: Handle Edge Cases

    • Empty array
    • Single element array
    • All elements same
    • Strictly increasing/decreasing sequences
  5. Step 5: Optimize and Verify

    • Ensure O(n) time complexity
    • Check space complexity
    • Verify with sample inputs
    • Handle integer overflow if needed

Common Mistakes & Tips

Common Mistakes

  • Wrong Stack Order: Using increasing stack for next greater problems (should be decreasing)
  • Index vs Value Confusion: Storing values when indices are needed for distance calculation
  • Incomplete Processing: Forgetting to process remaining elements in stack
  • Boundary Issues: Not handling empty stack cases properly
  • Circular Logic: Not processing circular arrays correctly (missing second pass)
  • Condition Errors: Using wrong comparison operators (< vs <=, > vs >=)

Best Practices

  • Always store indices when you need position information
  • Use sentinel values (like 0) to simplify boundary handling
  • Process from left to right unless specifically need right-to-left
  • Clear variable names: stack, result, current_idx instead of s, res, i
  • Comment the while condition to clarify monotonic property
  • Handle edge cases first before main algorithm

Interview Tips

  1. Pattern Recognition

    • Listen for “next greater/smaller” keywords
    • Area/rectangle problems often use monotonic stacks
    • Sequence validation problems may need stack-based approaches
  2. Problem-Solving Approach

    • Start with brute force to understand the problem
    • Identify if monotonic property can optimize the solution
    • Draw examples to visualize stack behavior
  3. Communication During Interview

    • Explain why monotonic stack is appropriate
    • Walk through the stack state with examples
    • Discuss time/space complexity trade-offs
  4. Implementation Tips

    • Start with the template structure
    • Focus on getting the while condition right
    • Test with simple examples (like [2,1,2,4,3,1])
  5. Follow-up Questions to Expect

    • How to handle duplicates?
    • What if we need previous instead of next?
    • Can you optimize space complexity?
    • How to extend to 2D problems?
  • Stack: Monotonic stack is a specialized application of stack data structure
  • Deque: Monotonic deque for sliding window maximum problems
  • Two Pointers: Alternative approach for some area calculation problems
  • Dynamic Programming: Some optimization problems combine DP with monotonic stacks
  • Binary Search: Finding boundaries in sorted structures
  • Segment Tree: Advanced queries on range maximum/minimum

LC Examples

2-1) Daily Temperatures (LC 739) — Monotonic Decreasing Stack

Stack stores indices; pop when a warmer day is found.

java
// LC 739 - Daily Temperatures
// IDEA: Monotonic decreasing stack — pop when current > stack top
// time = O(N), space = O(N)
public int[] dailyTemperatures(int[] temperatures) {
    int n = temperatures.length;
    int[] ans = new int[n];
    Deque<Integer> stack = new ArrayDeque<>(); // stores indices
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
            int idx = stack.pop();
            ans[idx] = i - idx;
        }
        stack.push(i);
    }
    return ans;
}

2-2) Largest Rectangle in Histogram (LC 84) — Monotonic Increasing Stack

Pop a bar when a shorter bar arrives; compute area using width from stack.

java
// LC 84 - Largest Rectangle in Histogram
// IDEA: Monotonic increasing stack — pop and compute area on shorter bar
// time = O(N), space = O(N)
public int largestRectangleArea(int[] heights) {
    Deque<Integer> stack = new ArrayDeque<>();
    int maxArea = 0;
    for (int i = 0; i <= heights.length; i++) {
        int h = (i == heights.length) ? 0 : heights[i];
        while (!stack.isEmpty() && h < heights[stack.peek()]) {
            int height = heights[stack.pop()];
            int width = stack.isEmpty() ? i : i - stack.peek() - 1;
            maxArea = Math.max(maxArea, height * width);
        }
        stack.push(i);
    }
    return maxArea;
}

2-3) Next Greater Element I (LC 496) — Monotonic Stack + HashMap

Precompute next greater element for nums2, then answer queries for nums1.

java
// LC 496 - Next Greater Element I
// IDEA: Monotonic decreasing stack on nums2; store results in map
// time = O(M + N), space = O(M)
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
    Map<Integer, Integer> map = new HashMap<>(); // val -> next greater val
    Deque<Integer> stack = new ArrayDeque<>();
    for (int num : nums2) {
        while (!stack.isEmpty() && num > stack.peek()) {
            map.put(stack.pop(), num);
        }
        stack.push(num);
    }
    int[] ans = new int[nums1.length];
    for (int i = 0; i < nums1.length; i++) {
        ans[i] = map.getOrDefault(nums1[i], -1);
    }
    return ans;
}

2-4) Trapping Rain Water (LC 42) — Monotonic Stack

Pop a bar when a taller bar arrives; water trapped = (min height difference) * width.

java
// LC 42 - Trapping Rain Water
// IDEA: Monotonic stack — pop when taller bar found, water fills between boundaries
// time = O(N), space = O(N)
public int trap(int[] height) {
    Deque<Integer> stack = new ArrayDeque<>();
    int water = 0;
    for (int i = 0; i < height.length; i++) {
        while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
            int bottom = stack.pop();
            if (stack.isEmpty()) break;
            int left = stack.peek();
            int width = i - left - 1;
            int boundedHeight = Math.min(height[left], height[i]) - height[bottom];
            water += width * boundedHeight;
        }
        stack.push(i);
    }
    return water;
}

2-5) Next Greater Element II (LC 503) — Circular Monotonic Stack

Process array twice (or use modulo) to handle circular next-greater queries.

java
// LC 503 - Next Greater Element II (circular array)
// IDEA: Monotonic stack — traverse 2n indices with modulo for circular effect
// time = O(N), space = O(N)
public int[] nextGreaterElements(int[] nums) {
    int n = nums.length;
    int[] ans = new int[n];
    Arrays.fill(ans, -1);
    Deque<Integer> stack = new ArrayDeque<>();
    for (int i = 0; i < 2 * n; i++) {
        while (!stack.isEmpty() && nums[i % n] > nums[stack.peek()]) {
            ans[stack.pop()] = nums[i % n];
        }
        if (i < n) stack.push(i);
    }
    return ans;
}

2-6) Online Stock Span (LC 901) — Monotonic Decreasing Stack

Pop all previous prices <= current; span = days since last greater price.

java
// LC 901 - Online Stock Span
// IDEA: Monotonic decreasing stack storing [price, span] pairs
// time = O(1) amortized per call, space = O(N)
class StockSpanner {
    Deque<int[]> stack = new ArrayDeque<>(); // [price, span]
    public int next(int price) {
        int span = 1;
        while (!stack.isEmpty() && stack.peek()[0] <= price) {
            span += stack.pop()[1];
        }
        stack.push(new int[]{price, span});
        return span;
    }
}

2-7) Sum of Subarray Minimums (LC 907) — Monotonic Stack

For each element, find left/right boundaries where it is the minimum; use monotonic stack.

java
// LC 907 - Sum of Subarray Minimums
// IDEA: Monotonic stack — for each element find left & right span as minimum
// time = O(N), space = O(N)
public int sumSubarrayMins(int[] arr) {
    int n = arr.length;
    int MOD = 1_000_000_007;
    int[] left = new int[n], right = new int[n];
    Deque<Integer> stack = new ArrayDeque<>();
    // left[i] = distance to previous smaller element
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) stack.pop();
        left[i] = stack.isEmpty() ? i + 1 : i - stack.peek();
        stack.push(i);
    }
    stack.clear();
    // right[i] = distance to next smaller or equal element
    for (int i = n-1; i >= 0; i--) {
        while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) stack.pop();
        right[i] = stack.isEmpty() ? n - i : stack.peek() - i;
        stack.push(i);
    }
    long ans = 0;
    for (int i = 0; i < n; i++) ans = (ans + (long) arr[i] * left[i] * right[i]) % MOD;
    return (int) ans;
}

Contribution Method — Visualizing left[i] / right[i] (Python) Priority 5 of 5 — Must know — expect it in almost every loop

leetcode_python/Math/sum-of-subarray-minimums.py

Core idea: every subarray has exactly one minimum, so instead of enumerating subarrays we ask “for how many subarrays is arr[i] the minimum?” — then sum arr[i] * count.

For each index i, that count splits into two independent choices:

text
        left choices              right choices
           <---->                    <----->
   ┌───────────────────────────────────────────────┐
   │  ...  PSE   .   .   .   [i]   .   .   .   NSE   │      arr
   └───────────────────────────────────────────────┘
              ^                          ^
        previous smaller           next smaller-or-equal
        element (strict >=)        element (strict >)

   left[i]  = i - PSE     ← # of left endpoints that keep arr[i] as min
   right[i] = NSE - i     ← # of right endpoints that keep arr[i] as min

   count(i) = left[i] * right[i]
   contribution = arr[i] * left[i] * right[i]
  • A subarray keeps arr[i] as its minimum only if it starts somewhere in (PSE, i] and ends somewhere in [i, NSE).
  • The two ranges are independent → multiply them.

Handling duplicates (avoid double counting): use >= on the left pass and > on the right pass (asymmetric). Equal values are then counted on exactly one side.

python
# python
# LC 907 - Sum of Subarray Minimums (contribution method)
# time = O(n), space = O(n)
MOD = 10**9 + 7
n = len(arr)
left  = [0] * n   # left[i]  = distance to previous smaller element
right = [0] * n   # right[i] = distance to next smaller-or-equal element

# --- LEFT pass: distance to Previous Smaller Element (pop on >=) ---
mono_st = []
for i in range(n):
    val = arr[i]
    # Pop elements that are greater than OR EQUAL to current val
    while mono_st and arr[mono_st[-1]] >= val:
        mono_st.pop()   # these can't be the left boundary of arr[i]

    # If stack empty -> val is the smallest so far, boundary is index -1
    #   left choices = i - (-1) = i + 1
    # Else -> boundary is the surviving stack top (the PSE)
    #   left choices = i - mono_st[-1]
    left[i] = i + 1 if not mono_st else i - mono_st[-1]
    mono_st.append(i)

# --- RIGHT pass: distance to Next Smaller Element (pop on >) ---
mono_st = []
for i in range(n - 1, -1, -1):
    val = arr[i]
    while mono_st and arr[mono_st[-1]] > val:   # strict > here
        mono_st.pop()
    right[i] = n - i if not mono_st else mono_st[-1] - i
    mono_st.append(i)

ans = 0
for i in range(n):
    ans = (ans + arr[i] * left[i] * right[i]) % MOD

Why left[i] = i + 1 when the stack is empty: an empty stack means nothing to the left is smaller than arr[i]arr[i] dominates the whole prefix. The imaginary left boundary sits at index -1, so the left choices span indices 0..i, i.e. i - (-1) = i + 1.

Visual trace on arr = [3, 1, 2, 4]:

text
i=0 val=3 : stack empty              -> left[0] = 0-(-1) = 1   stack=[0]
i=1 val=1 : arr[0]=3 >= 1 -> pop 0
            stack empty              -> left[1] = 1-(-1) = 2   stack=[1]
i=2 val=2 : arr[1]=1 >= 2? no        -> left[2] = 2-1     = 1   stack=[1,2]
i=3 val=4 : arr[2]=2 >= 4? no        -> left[3] = 3-2     = 1   stack=[1,2,3]

left  = [1, 2, 1, 1]
right = [1, 3, 2, 1]   (symmetric backward pass with strict >)

contribution = 3*1*1 + 1*2*3 + 2*1*2 + 4*1*1 = 3 + 6 + 4 + 4 = 17  ✓

2-8) Remove K Digits (LC 402) — Monotonic Increasing Stack

Maintain increasing stack; remove digits when a smaller digit arrives.

java
// LC 402 - Remove K Digits
// IDEA: Greedy + monotonic increasing stack — remove larger digits greedily
// time = O(N), space = O(N)
public String removeKdigits(String num, int k) {
    Deque<Character> stack = new ArrayDeque<>();
    for (char c : num.toCharArray()) {
        while (k > 0 && !stack.isEmpty() && stack.peek() > c) {
            stack.pop(); k--;
        }
        stack.push(c);
    }
    while (k-- > 0) stack.pop(); // remove from top if k still > 0
    // reconstruct result in correct order (bottom to top of stack)
    Deque<Character> result = new ArrayDeque<>(stack);
    StringBuilder sb = new StringBuilder();
    boolean leadingZero = true;
    while (!result.isEmpty()) {
        char c = result.pollFirst();
        if (leadingZero && c == '0') continue;
        leadingZero = false;
        sb.append(c);
    }
    return sb.length() == 0 ? "0" : sb.toString();
}

2-9) Maximal Rectangle (LC 85) — Histogram + Monotonic Stack

For each row, compute histogram heights; apply LC 84 largest rectangle logic per row.

java
// LC 85 - Maximal Rectangle
// IDEA: For each row build histogram; apply largestRectangleArea (LC 84) logic
// time = O(M*N), space = O(N)
public int maximalRectangle(char[][] matrix) {
    if (matrix.length == 0) return 0;
    int n = matrix[0].length, maxArea = 0;
    int[] heights = new int[n];
    for (char[] row : matrix) {
        for (int j = 0; j < n; j++)
            heights[j] = row[j] == '0' ? 0 : heights[j] + 1;
        maxArea = Math.max(maxArea, largestRectangle(heights));
    }
    return maxArea;
}
private int largestRectangle(int[] heights) {
    Deque<Integer> stack = new ArrayDeque<>();
    int max = 0;
    for (int i = 0; i <= heights.length; i++) {
        int h = i == heights.length ? 0 : heights[i];
        while (!stack.isEmpty() && h < heights[stack.peek()]) {
            int height = heights[stack.pop()];
            int width = stack.isEmpty() ? i : i - stack.peek() - 1;
            max = Math.max(max, height * width);
        }
        stack.push(i);
    }
    return max;
}

2-10) The Car Fleet Family (LC 853 / 4045 / 1776) — When a “Stack” Is Really a Greedy Priority 4 of 5 — High value — a gap here costs you rounds

leetcode_python/Stack/car-fleet.py, leetcode_python/Stack/count-robot-groups.py

The section in this file with a negative lesson. LC 853 is taught as a stack problem and tagged **stack** in this repo’s README — but its stack never pops, so it is a two-variable greedy in a stack costume. Of the three problems here only LC 1776 needs a real stack. Being able to say which, and why, is the whole signal.

The property all three share: the front dominates

text
nobody can pass the entity ahead of it
  -> you can only ever merge with the entity DIRECTLY ahead
  -> a merged group moves at the FRONT member's speed (the rear one slows down)

so: scan RIGHT -> LEFT (front -> back), and the survivors appear in order

That last line is why a single “frontmost survivor so far” variable is enough in 853 and 4045: a survivor is never revisited once found, and a stack is only worth its name when you have to go back to an older candidate.

LC 853 Car Fleet — the stack that never pops

There is a finish line at target, so each car collapses to one number — the time it would arrive if nothing were in its way:

text
t_i = (target - position[i]) / speed[i]

A car joins the fleet ahead iff it would arrive no later than that fleet, i.e. t_i <= max(t of everything ahead of it). So sort front-first and count how many times a new maximum appears:

python
# python
# LC 853 - Car Fleet  (the textbook "stack" version)
# time = O(n log n), space = O(n)
def carFleet(target, position, speed):
    st = []
    for p, s in sorted(zip(position, speed), reverse=True):   # front -> back
        t = (target - p) / s
        if not st or t > st[-1]:      # cannot catch the fleet ahead -> new fleet
            st.append(t)
        # else: t <= st[-1], it catches up and is absorbed — nothing to record
    return len(st)

Look at what that loop never does: it never pops. So st is increasing, st[-1] is simply the largest t seen so far, and len(st) is simply how many times that maximum went up. Two variables do the same job:

python
# python
# LC 853 - the same algorithm, stack removed
# time = O(n log n) (the sort), space = O(1)
def carFleet(target, position, speed):
    cnt, mx = 0, 0.0
    for p, s in sorted(zip(position, speed), reverse=True):   # front -> back
        t = (target - p) / s
        if t > mx:                    # arrives later than anyone ahead -> its own fleet
            cnt += 1
            mx = t
    return cnt
java
// java
// LC 853 - Car Fleet, running-max greedy
// time = O(N log N), space = O(N) for the index array (O(1) beyond the sort)
public int carFleet(int target, int[] position, int[] speed) {
    int n = position.length;
    Integer[] idx = new Integer[n];
    for (int i = 0; i < n; i++) idx[i] = i;
    Arrays.sort(idx, (a, b) -> position[b] - position[a]);   // front -> back
    int cnt = 0;
    double mx = 0.0;
    for (int i : idx) {
        double t = (double) (target - position[i]) / speed[i];
        if (t > mx) { cnt++; mx = t; }
    }
    return cnt;
}

Both versions agree on random inputs, as they must — they are the same algorithm. Keep whichever you can write under pressure; just do not claim the stack is doing any work.

LC 4045 Count Robot Groups — the same greedy, with no finish line

Robots move forever and there is a merge threshold distance. No finish line means no arrival time exists, so there is no single number per robot to compare — the state is the pair (position, speed) of a group’s rightmost robot. Robot i is absorbed if either test fires:

text
position[i+1] - position[i] <= distance   -> already touching at t = 0
speed[i] > cur_s                          -> closing on the front group, and with
                                             infinite time ANY positive closing rate
                                             eventually eats ANY gap
python
# python
# LC 4045 - Count Robot Groups   (position is given sorted, so no sort is needed)
# IDEA: right -> left; `cur_s` is the speed of the frontmost group that survived.
# time = O(n), space = O(1)
def countGroups(position, speed, distance):
    n = len(position)
    cur_s = speed[n - 1]           # the frontmost robot is always a group on its own
    cnt = 1
    for i in range(n - 2, -1, -1):
        # touching test -> the NEIGHBOUR;  closing test -> the FRONT group
        if position[i + 1] - position[i] <= distance or speed[i] > cur_s:
            continue               # absorbed
        cnt += 1
        cur_s = speed[i]           # a new frontmost survivor
    return cnt

The trap: the two tests use different references. A merge adopts the rightmost robot’s state, so cur does not move when someone joins from behind — but the t = 0 merges are simultaneous and chain through neighbours:

text
position = [18,19,22,24], distance = 3     gaps: 1, 3, 2  -> every pair touches
all four collapse at t = 0, even though 18 is 6 away from the group's position 24

Test the t = 0 touch against cur_p instead of position[i+1] and those chains vanish silently. The speed test is safe against cur on its own: if robot i+1 survived only because it was faster than the front, and i is faster still, then i is faster than the front too — catching your neighbour implies catching the front group.

853 vs 4045 side by side

LC 853 Car Fleet LC 4045 Count Robot Groups
Horizon finish line at target none — robots run forever
Per-entity state one scalar: t = (target - p) / s the pair (position, speed); no scalar exists
Merge test t_i <= max(t ahead) — compare times speed[i] > cur_s — compare speeds
Merge threshold bumper to bumper (gap 0) gap <= distance, plus the t = 0 touch rule
Input order positions unsorted → must sort position given sorted → no sort
Merged group takes the slower (front) car’s speed the rightmost robot’s position and speed
Cost O(n log n) / O(1) beyond the sort O(n) / O(1)
Stack needed? no — append-only no — two variables

Why 853 gets a scalar and 4045 does not is the one idea to carry away:

  • 853 has a deadline, so “does the rear catch the front?” becomes “does it arrive no later?” — a comparison of one number per car. Two cars can be closing on each other all the way and still be separate fleets, because they would have met past target. The deadline is what creates the scalar, and the scalar is what makes the running max work.
  • 4045 has no deadline, so a closing rate is all that matters: given infinite time, speed[i] > cur_s already is the answer — there is no arithmetic to do, because nobody ever arrives.

LC 1776 Car Fleet II — the member that needs a real stack

Same road, same “front dominates” rule, but the question changes: report the collision time for every car, not a count. That breaks the single-survivor invariant, because the car ahead is itself absorbed at a known time ans[j]:

text
if car i reaches car j LATER than j's own collision time
    -> j is already gone when i gets there
    -> i's real target is whatever is further ahead
    -> back up to an older candidate        <- THIS is a pop
python
# python
# LC 1776 - Car Fleet II: ans[i] = when car i collides with the car ahead (-1 = never)
# IDEA: right -> left monotonic stack of cars still catchable. Pop a candidate that
#       cannot be caught, or that dies before we reach it.
# time = O(n), space = O(n)
def getCollisionTimes(cars):
    n = len(cars)
    ans = [-1.0] * n
    st = []                                     # indices, front -> back
    for i in range(n - 1, -1, -1):
        p, s = cars[i]
        while st:
            j = st[-1]
            pj, sj = cars[j]
            # (a) not faster than j -> can never reach it
            # (b) reaching j takes longer than j survives -> aim further ahead
            if s <= sj or (ans[j] > 0 and (pj - p) / (s - sj) >= ans[j]):
                st.pop()
            else:
                break
        if st:
            j = st[-1]
            ans[i] = (cars[j][0] - p) / (s - cars[j][1])
        st.append(i)
    return ans

# cars = [[1,2],[2,1],[4,3],[7,2]] -> [1.0, -1.0, 3.0, -1.0]
# cars = [[3,4],[5,4],[6,3],[9,1]] -> [2.0, 1.0, 1.5, -1.0]

The rule to take into the room

You are asked for Front’s behaviour is Structure
a count of surviving groups (853, 4045) permanent — a survivor is never revisited one or two variables (greedy)
a per-element time or value (1776, 2289) temporary — the front itself dies at a known time a real stack, with pops

So the honest one-liner for an interviewer: “853 and 4045 are the same right-to-left greedy — the usual stack solution for 853 never pops, so I will keep the running maximum instead. 1776 is where the stack earns its place, because an already-computed answer can be invalidated.”

2-11) Asteroid Collision (LC 735) — Stack Simulation

Right-moving asteroids stay on stack; left-moving collide with top until stable.

java
// LC 735 - Asteroid Collision
// IDEA: Stack — simulate collisions between right (+) and left (-) asteroids
// time = O(N), space = O(N)
public int[] asteroidCollision(int[] asteroids) {
    Deque<Integer> stack = new ArrayDeque<>();
    for (int a : asteroids) {
        boolean alive = true;
        while (alive && a < 0 && !stack.isEmpty() && stack.peek() > 0) {
            if (stack.peek() < -a) { stack.pop(); }      // stack top destroyed
            else if (stack.peek() == -a) { stack.pop(); alive = false; } // both destroyed
            else alive = false;                             // incoming destroyed
        }
        if (alive) stack.push(a);
    }
    int[] res = new int[stack.size()];
    for (int i = res.length - 1; i >= 0; i--) res[i] = stack.pop();
    return res;
}

2-12) Sum of Subarray Ranges (LC 2104) — Dual Monotonic Stack (Contribution Method)

sum(ranges) = sum(subarray maxs) − sum(subarray mins). Use one monotonic stack pass per role; for each popped element compute how many subarrays it owns as the max/min.

Core Idea

text
range(subarray) = max − min
sum(all ranges) = sum(all subarray maxs) − sum(all subarray mins)

For each element nums[mid], find its left and right dominance boundaries:

  • Left boundary L — index of the previous element that would displace nums[mid] from the max/min role (or -1 if none)
  • Right boundary R — index of the next element that displaces it (or n if none)

Number of subarrays where nums[mid] is the max/min:

text
count = (mid − L) × (R − mid)
contribution = nums[mid] × count

The sentinel loop runs i from 0 to n inclusive. When i == n, it flushes every remaining index from the stack using n as the right boundary.

Duplicate-safe boundary rule (avoids double-counting equal elements):

  • For max: pop when nums[mid] < nums[i] (strict); left boundary is the last greater-or-equal element.
  • For min: pop when nums[mid] > nums[i] (strict); left boundary is the last smaller-or-equal element.

Visual Trace — max pass on [1, 3, 2]

text
Decreasing stack (max contribution)

i=0: push 0         stack=[0]
i=1: nums[0]=1 < nums[1]=3 → pop mid=0
       left=-1, right=1
       contrib = 1 * (0-(-1)) * (1-0) = 1*1*1 = 1
     push 1          stack=[1]
i=2: nums[1]=3 > nums[2]=2, no pop
     push 2          stack=[1,2]
i=3 (sentinel): flush
     pop mid=2: left=1, right=3  → 2*(2-1)*(3-2) = 2
     pop mid=1: left=-1, right=3 → 3*(1-(-1))*(3-1) = 12

max_sum = 1 + 2 + 12 = 15

min pass (increasing stack) → min_sum = 10

answer = 15 − 10 = 5  ✓
verify: [1]=0,[3]=0,[2]=0,[1,3]=2,[3,2]=1,[1,3,2]=2 → sum = 5

Pattern (Python)

python
# python
# LC 2104 - Sum of Subarray Ranges
# IDEA: sum(ranges) = sum(subarray maxs) - sum(subarray mins)
#       Contribution method via monotonic stack — one pass per role
# time = O(N), space = O(N)
def subArrayRanges(nums):
    n = len(nums)

    def contribution(is_max):
        stack = []
        total = 0
        for i in range(n + 1):          # sentinel: i == n flushes remaining
            while stack and (
                i == n or
                (nums[stack[-1]] < nums[i] if is_max else nums[stack[-1]] > nums[i])
            ):
                mid = stack.pop()
                left  = stack[-1] if stack else -1   # previous boundary index
                right = i                            # current index = right boundary
                total += nums[mid] * (mid - left) * (right - mid)
            stack.append(i)
        return total

    return contribution(True) - contribution(False)

Pattern (Java)

java
// java
// LC 2104 - Sum of Subarray Ranges
// IDEA: sum(ranges) = sum(subarray maxs) - sum(subarray mins)
//       Contribution method: for each element count subarrays where it's max/min
// time = O(N), space = O(N)
public long subArrayRanges(int[] nums) {
    return contribution(nums, true) - contribution(nums, false);
}

private long contribution(int[] nums, boolean isMax) {
    int n = nums.length;
    Deque<Integer> stack = new ArrayDeque<>();
    long total = 0;

    for (int i = 0; i <= n; i++) {          // i == n is the sentinel flush
        while (!stack.isEmpty()) {
            int mid = stack.peek();
            boolean shouldPop = (i == n) ||
                (isMax ? nums[mid] < nums[i] : nums[mid] > nums[i]);
            if (!shouldPop) break;
            stack.pop();
            int left  = stack.isEmpty() ? -1 : stack.peek(); // prev boundary
            int right = i;                                    // next boundary
            total += (long) nums[mid] * (mid - left) * (right - mid);
        }
        stack.push(i);
    }
    return total;
}

Two-Stack Logic Summary

Pass Stack type Pop condition Computes
Max pass Monotonic decreasing nums[mid] < nums[i] Sum of subarray maximums
Min pass Monotonic increasing nums[mid] > nums[i] Sum of subarray minimums
Both Sentinel at i = n Always flush Handles right-edge elements

Similar Problems

Problem LC# Key Difference
Sum of Subarray Ranges 2104 max_sum − min_sum; two monotonic stack passes
Sum of Subarray Minimums 907 Min contribution only; single increasing stack pass
Maximum Subarray Min-Product 1856 Min contribution × subarray sum; prefix sums + stack
Sum of Total Strength of Wizards 2281 Min × sum of sums; prefix of prefix sums + stack
Largest Rectangle in Histogram 84 Area = height × width; pop on shorter bar
Number of Visible People in Queue 1944 Count pops per element as the answer

2-13) Longest Absolute File Path (LC 388) — Stack Indexed by Nesting Depth Priority 5 of 5 — Must know — expect it in almost every loop

Template 7: depth stack. The stack is not monotonic by value — it is monotonic by depth: stack[d] always holds the accumulated path length at depth d. Before handling a line at depth d, pop until stack.size() == d, which discards every sibling branch that just ended.

Key idea

text
"dir\n\tsub1\n\t\tfile.ext\n\tsub2"

line          depth   pop until size==depth   stack (path lengths, '/' included)
dir             0     []                      [4]            "dir/"
  sub1          1     [4]                     [4, 9]         "dir/sub1/"
    file.ext    2     [4, 9]                  (file → no push, len = 9 + 8 = 17)
  sub2          1     pop 9 → [4]             [4, 9]
  • depth = number of leading \t; the name is the rest of the line.
  • A directory pushes parentLen + name.length() + 1 (the +1 is the / separator).
  • A file (name contains .) never pushes — it only updates the answer with parentLen + name.length().
java
// java
// LC 388 - Longest Absolute File Path
// IDEA: Stack indexed by nesting depth — stack.peek() = length of the current
//       directory prefix (with trailing '/'); pop until size == depth to leave sibling branches
// time = O(N), space = O(D)  // N = input length, D = max depth
public int lengthLongestPath(String input) {
    Deque<Integer> stack = new ArrayDeque<>(); // prefix length per depth
    int maxLen = 0;
    for (String line : input.split("\n")) {
        int depth = line.lastIndexOf('\t') + 1;  // tabs are leading & contiguous
        String name = line.substring(depth);
        while (stack.size() > depth) stack.pop();          // leave finished branches
        int parentLen = stack.isEmpty() ? 0 : stack.peek();
        int curLen = parentLen + name.length();
        if (name.indexOf('.') >= 0) {
            maxLen = Math.max(maxLen, curLen);             // file → candidate answer
        } else {
            stack.push(curLen + 1);                        // dir → +1 for '/'
        }
    }
    return maxLen;
}
python
# python
# LC 388 - Longest Absolute File Path
# IDEA: stack[d] = length of the directory prefix at depth d (trailing '/' counted);
#       pop until len(stack) == depth so sibling branches are discarded
# time = O(N), space = O(D)
def lengthLongestPath(input: str) -> int:
    stack = []          # prefix length per depth
    best = 0
    for line in input.split('\n'):
        depth = len(line) - len(line.lstrip('\t'))
        name = line[depth:]
        while len(stack) > depth:
            stack.pop()
        parent = stack[-1] if stack else 0
        cur = parent + len(name)
        if '.' in name:
            best = max(best, cur)      # file
        else:
            stack.append(cur + 1)      # dir, +1 for '/'
    return best

Pitfalls

  • The answer is the longest path to a file, so never update the max on a directory.
  • Do not compute depth with line.count('\t') after slicing — depth must come from the leading tabs only.
  • Empty input / no file → return 0.

2-14) Longest Valid Parentheses (LC 32) — Index Stack with a Base Sentinel Priority 5 of 5 — Must know — expect it in almost every loop

Template 8: stack of indices + sentinel base. Instead of storing characters, store indices, and seed the stack with -1 as “the index just before the current valid block”. After popping on ), the new stack top is the last unmatched index, so i - stack.peek() is the length of the valid run ending at i — no extra length bookkeeping needed.

Two cases on )

text
pop, then:
  stack empty  → this ')' is unmatched → push i as the NEW base
  stack !empty → length = i - stack.top()

Visual trace on s = ")()())"

text
i=0 ')'  pop -1 → empty → push 0        stack=[0]        best=0
i=1 '('  push 1                          stack=[0,1]
i=2 ')'  pop 1 → top=0 → 2-0 = 2         stack=[0]        best=2
i=3 '('  push 3                          stack=[0,3]
i=4 ')'  pop 3 → top=0 → 4-0 = 4         stack=[0]        best=4
i=5 ')'  pop 0 → empty → push 5          stack=[5]        best=4  ✓
java
// java
// LC 32 - Longest Valid Parentheses
// IDEA: Stack of indices seeded with -1 (base). On ')' pop; if empty this ')' becomes
//       the new base, else answer candidate = i - stack.peek()
// time = O(N), space = O(N)
public int longestValidParentheses(String s) {
    Deque<Integer> stack = new ArrayDeque<>();
    stack.push(-1);                 // base = index before the current valid block
    int best = 0;
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            stack.push(i);
        } else {
            stack.pop();
            if (stack.isEmpty()) stack.push(i);                 // unmatched ')' → new base
            else best = Math.max(best, i - stack.peek());
        }
    }
    return best;
}
python
# python
# LC 32 - Longest Valid Parentheses
# IDEA: index stack with -1 sentinel; i - stack[-1] = length of valid run ending at i
# time = O(N), space = O(N)
def longestValidParentheses(s: str) -> int:
    stack = [-1]          # base index
    best = 0
    for i, c in enumerate(s):
        if c == '(':
            stack.append(i)
        else:
            stack.pop()
            if not stack:
                stack.append(i)               # new base
            else:
                best = max(best, i - stack[-1])
    return best

Pitfalls

  • Forgetting the -1 seed breaks every run that starts at index 0.
  • The stack holds indices, never characters — the whole trick is the index arithmetic.
  • O(1)-space alternative: two passes (left→right, then right→left) with open/close counters, resetting when close > open (resp. open > close).

2-15) Maximum Binary Tree (LC 654) — Monotonic Decreasing Stack Builds a Cartesian Tree Priority 4 of 5 — High value — a gap here costs you rounds

Template 9: monotonic stack that builds a tree. The naive “find max, recurse left/right” is O(n²). A decreasing stack builds the same tree in one pass: everything popped by num is smaller than num and sits to its left → it becomes num’s left subtree; the surviving stack top is greater than numnum becomes its right child. Root = bottom of the stack.

text
nums = [3,2,1,6,0,5]

3 → stack[3]
2 → 3>2, 3.right = 2            stack[3,2]
1 → 2>1, 2.right = 1            stack[3,2,1]
6 → pop 1,2,3 (each becomes 6.left in turn, last popped wins) → stack empty
                                 stack[6]      root = 6
0 → 6.right = 0                 stack[6,0]
5 → pop 0 → 5.left = 0; top 6 → 6.right = 5   stack[6,5]
java
// java
// LC 654 - Maximum Binary Tree
// IDEA: Monotonic DECREASING stack of nodes. Nodes popped by num become num's left
//       subtree (last popped = direct left child); surviving top adopts num as right child
// time = O(N), space = O(N)   // beats the O(N^2) divide & conquer build
public TreeNode constructMaximumBinaryTree(int[] nums) {
    Deque<TreeNode> stack = new ArrayDeque<>(); // values decreasing: bottom -> top
    for (int num : nums) {
        TreeNode cur = new TreeNode(num);
        while (!stack.isEmpty() && stack.peek().val < num) {
            cur.left = stack.pop();          // last popped ends up as the left child
        }
        if (!stack.isEmpty()) stack.peek().right = cur;
        stack.push(cur);
    }
    return stack.isEmpty() ? null : stack.peekLast(); // bottom of stack = global max = root
}
python
# python
# LC 654 - Maximum Binary Tree
# IDEA: monotonic decreasing stack of nodes; popped nodes chain into cur.left,
#       remaining top takes cur as its right child; stack[0] is the root
# time = O(N), space = O(N)
def constructMaximumBinaryTree(nums):
    stack = []                      # node values decreasing
    for num in nums:
        cur = TreeNode(num)
        while stack and stack[-1].val < num:
            cur.left = stack.pop()  # overwritten each pop -> keeps the LAST popped
        if stack:
            stack[-1].right = cur
        stack.append(cur)
    return stack[0] if stack else None

Why cur.left may be overwritten: each pop re-assigns cur.left, and the popped nodes are already linked to each other (an earlier pop is the previous node’s right child), so after the loop cur.left correctly points at the root of the whole popped block.

Related: LC 1008 (Construct BST from Preorder Traversal) uses the mirror idea — a decreasing stack where a larger value becomes the right child of the last popped node.

2-16) Min Stack (LC 155) — Auxiliary Non-Increasing Stack Priority 4 of 5 — High value — a gap here costs you rounds

Template 10: parallel “min stack”. Keep a second stack whose values are non-increasing; its top is always the minimum of the live elements. This is the design-problem face of the monotonic stack.

java
// java
// LC 155 - Min Stack
// IDEA: second stack keeps a non-increasing sequence of minima; push a new min when
//       val <= current min (the '=' is REQUIRED so duplicates survive matching pops)
// time = O(1) per op, space = O(N)
class MinStack {
    private final Deque<Integer> stack = new ArrayDeque<>();
    private final Deque<Integer> mins  = new ArrayDeque<>(); // non-increasing

    public void push(int val) {
        stack.push(val);
        if (mins.isEmpty() || val <= mins.peek()) mins.push(val);
    }
    public void pop() {
        int v = stack.pop();
        if (v == mins.peek()) mins.pop();
    }
    public int top()    { return stack.peek(); }
    public int getMin() { return mins.peek(); }
}
python
# python
# LC 155 - Min Stack
# IDEA: auxiliary non-increasing stack of minima; '<=' on push keeps duplicate minima
# time = O(1) per op, space = O(N)
class MinStack:
    def __init__(self):
        self.stack = []
        self.mins  = []          # non-increasing

    def push(self, val: int) -> None:
        self.stack.append(val)
        if not self.mins or val <= self.mins[-1]:
            self.mins.append(val)

    def pop(self) -> None:
        if self.stack.pop() == self.mins[-1]:
            self.mins.pop()

    def top(self) -> int:
        return self.stack[-1]

    def getMin(self) -> int:
        return self.mins[-1]

The classic bug: pushing the new min only when val < mins.peek() (strict). With push(0); push(0); pop(); the single stored 0 is removed and getMin() returns the wrong value. Use <=. Space-saving variant: store (val, minSoFar) pairs in one stack — same O(1) ops, simpler to state under interview pressure.

2-17) Variations of Existing Templates

LC # Problem Base template The twist
1475 Final Prices With a Special Discount in a Shop Template 2 (next smaller) Next smaller or equal — pop on prices[stack[-1]] >= prices[i], and the discount is price - prices[i] rather than the index distance
1019 Next Greater Node In Linked List Template 1 (next greater) Same decreasing stack, but the input is a linked list — walk it once into an array (or push (index, val) while walking) since the answer array needs random access
768 Max Chunks To Make Sorted II Template 1 (decreasing pops) Stack holds chunk maxima, not raw elements; answer = final stack size
769 Max Chunks To Make Sorted Template 1 (degenerate) Values are a permutation of 0..n-1, so a running max replaces the stack: cut a chunk whenever runningMax == i
1047 / 1209 Remove All Adjacent Duplicates In String (I / II) Template 5 (stack with info) Stack stores (char, count) pairs; pop when count reaches k — LC 1047 is the k = 2 special case

Max Chunks To Make Sorted II (LC 768) — chunk-maxima stack

java
// java
// LC 768 - Max Chunks To Make Sorted II
// IDEA: monotonic increasing stack of chunk MAXIMA. A value smaller than the top must
//       merge every chunk it is smaller than; the merged chunk keeps the largest max
// time = O(N), space = O(N)
public int maxChunksToSorted(int[] arr) {
    Deque<Integer> stack = new ArrayDeque<>(); // chunk maxima, increasing bottom -> top
    for (int num : arr) {
        if (!stack.isEmpty() && num < stack.peek()) {
            int maxOfMerged = stack.pop();
            while (!stack.isEmpty() && num < stack.peek()) stack.pop();
            stack.push(maxOfMerged);           // merged chunk keeps the old max
        } else {
            stack.push(num);                   // starts a new chunk
        }
    }
    return stack.size();
}
python
# python
# LC 768 - Max Chunks To Make Sorted II
# IDEA: increasing stack of chunk maxima; merging keeps the largest max
# time = O(N), space = O(N)
def maxChunksToSorted(arr):
    stack = []                       # chunk maxima, increasing
    for num in arr:
        if stack and num < stack[-1]:
            merged_max = stack.pop()
            while stack and num < stack[-1]:
                stack.pop()
            stack.append(merged_max)
        else:
            stack.append(num)
    return len(stack)

# LC 769 - Max Chunks To Make Sorted (values are a permutation of 0..n-1)
# time = O(N), space = O(1)
def maxChunksToSortedI(arr):
    chunks, running_max = 0, -1
    for i, num in enumerate(arr):
        running_max = max(running_max, num)
        if running_max == i:         # prefix holds exactly the values 0..i
            chunks += 1
    return chunks

2-18) Steps to Make Array Non-decreasing (LC 2289) — Monotonic Stack Carrying a DP Value Priority 4 of 5 — High value — a gap here costs you rounds

leetcode_python/Stack/steps-to-make-array-non-decreasing.py

Template 11: the stack carries a dp value, not just a position. Every template above computes a final number at the moment of the pop (a width, an area, a distance). Here the popped element hands its dp value up to the element that popped it, so the answers chain: dp[i] = max(dp of everything I popped) + 1. The answer is max(dp), the longest such chain.

Core Idea

One round deletes every nums[i] with nums[i-1] > nums[i], all at once. Simulating the rounds is O(n²) (n up to 1e5), so re-ask the question per element:

text
dp[i] = the round in which nums[i] is deleted     (0 = never deleted)
answer = max(dp)

nums[i] is eventually deleted by the nearest element on its left that is strictly greater — but that killer cannot reach nums[i] until everything between them is gone. That waiting time is exactly what the stack hands over:

text
keep a DECREASING stack of indices, then for each i:

  while nums[i] >= nums[stack[-1]]:        # this top can never kill me
      cur = max(cur, dp[stack.pop()])      #   -> inherit its deadline

  if stack:      dp[i] = cur + 1           # top is > nums[i]: my killer.
                                           #   it reaches me one round after
                                           #   the last of the popped ones died
  else:          dp[i] = 0                 # nothing bigger on the left -> I survive

Two things the “eating” metaphor makes obvious:

  • What gets popped is what I outlive. Anything <= nums[i] is doomed no later than nums[i] is, so its deadline is a lower bound on mine.
  • +1, not + popped count. All deletions in a round happen simultaneously, so a whole block of popped elements can vanish in the same round; only the slowest one (max, not sum) delays my killer, and then by exactly one round.

Reading the Stack: What steps_to_remove Means

The stack is easiest to hold in your head as value/deadline pairs rather than indices:

text
stack = [ [val, steps_to_remove], ... ]

val             = the element's value
steps_to_remove = WHICH ROUND this element is deleted in   (0 = never)

That second number is not a count of work, not a distance, not a nesting depth — it is a round number on a calendar. Read the ladder off it:

steps_to_remove Meaning Why
0 never deleted nothing strictly greater sits to its left (it is a prefix maximum), so no element can ever delete it
1 deleted in round 1 its left neighbour is already strictly greater — the rule fires immediately, nobody has to clear the way
2 deleted in round 2 the block between it and its killer needs 1 round to disappear; it is exposed at the start of round 2
3 deleted in round 3 that block needs 2 rounds; only then does the killer become its left neighbour
k deleted in round k the block in front of it takes k - 1 rounds to clear, and it dies in the next one

So steps_to_remove = k always means k - 1 rounds ahead of me, then my turn” — which is exactly why the recurrence is max(popped) + 1 and why the answer is max over all the deadlines: the array is non-decreasing the round after the last scheduled deletion happens.

One consequence worth stating, because it is the usual off-by-one: an element whose deadline is 0 is not an element deleted in round 0. It is an element with no deadline at all, which is why the code assigns 0 in the stack is empty branch and never feeds it into res.

Visual Trace — nums = [5,3,4,4,7]

text
i  nums[i]  pops (dp inherited)          stack after       dp[i]                 res
0    5      -                            [0]               0   (nothing bigger)   0
1    3      none (3 < 5)                 [0,1]             0+1 = 1                1
2    4      pop 1 -> cur=max(0,dp1)=1    [0,2]             1+1 = 2                2
3    4      pop 2 -> cur=max(0,dp2)=2    [0,3]             2+1 = 3                3
4    7      pop 3 (cur=3), pop 0         [4]               0   (stack emptied)     3

dp = [0,1,2,3,0]  ->  answer 3
check: round1 deletes 3 -> [5,4,4,7]; round2 deletes the first 4 -> [5,4,7];
       round3 deletes the second 4 -> [5,7]  ✓

Dry Run — the Full LC Example, (val, steps) Pair Stack

nums = [5,3,4,4,7,3,6,11,8,5,11], expected answer 3. Same algorithm, written with pairs so the deadline is visible on the stack itself. Pop while stack[-1][0] <= val, inherit max(popped steps), then +1 if something is left (else 0):

# val stack before popped steps stack after res
0 5 [] 0 — stack empty, prefix max, never dies [(5,0)] 0
1 3 [(5,0)] 0 + 1 = 15 > 3 already, dies next round [(5,0),(3,1)] 1
2 4 [(5,0),(3,1)] (3,1) 1 + 1 = 2 — must outlive the 3, then 5 takes it [(5,0),(4,2)] 2
3 4 [(5,0),(4,2)] (4,2) 2 + 1 = 3 — popped on <=; inherits deadline 2 [(5,0),(4,3)] 3
4 7 [(5,0),(4,3)] (4,3), (5,0) 0 — stack emptied, new prefix max [(7,0)] 3
5 3 [(7,0)] 0 + 1 = 1 [(7,0),(3,1)] 3
6 6 [(7,0),(3,1)] (3,1) 1 + 1 = 2 [(7,0),(6,2)] 3
7 11 [(7,0),(6,2)] (6,2), (7,0) 0 — emptied again [(11,0)] 3
8 8 [(11,0)] 0 + 1 = 1 [(11,0),(8,1)] 3
9 5 [(11,0),(8,1)] 0 + 1 = 18 > 5 already; the 8 is irrelevant to the 5’s deadline [(11,0),(8,1),(5,1)] 3
10 11 [(11,0),(8,1),(5,1)] (5,1), (8,1), (11,0) 0 [(11,0)] 3

Result max(steps) = 3.

Now read the deadlines back as a schedule and compare with the rounds the problem statement prints:

text
idx     0  1  2  3  4  5  6  7  8  9 10
nums    5  3  4  4  7  3  6 11  8  5 11
steps   0  1  2  3  0  1  2  0  1  1  0
                                          <- every nonzero entry is a scheduled funeral

round 1  deletes steps==1  -> idx 1,5,8,9  (3, 3, 8, 5)
         [5,3,4,4,7,3,6,11,8,5,11] -> [5,4,4,7,6,11,11]

round 2  deletes steps==2  -> idx 2,6      (the first 4, and 6)
         [5,4,4,7,6,11,11] -> [5,4,7,11,11]

round 3  deletes steps==3  -> idx 3        (the second 4)
         [5,4,7,11,11] -> [5,7,11,11]      non-decreasing, stop

3 rounds  ==  max(steps)   ✓

Two rows are worth pausing on, because they are where the intuition usually breaks:

  • Row 3 (the second 4). It is popped on <=, not <. Its own deadline is not 1 even though a 5 sits to its left, because the first 4 is standing in the way until round 2 — hence 3. This is the row a strict > pop gets wrong, and the row that produces the whole answer.
  • Row 9 (the 5 after the 8). Four elements are alive on the stack, yet the 5 dies in round 1: its immediate left neighbour 8 is already greater, so nothing has to be cleared first. A deep stack does not imply a late deadline — the deadline only depends on what stands between an element and its killer.

And rows 4, 7, 10 show the other half of the invariant: whenever the stack empties, the new element is a prefix maximum, gets deadline 0, and every deadline behind it has already been banked into res — the array in effect restarts from that element.

The >= vs > trap

The deletion rule fires only on nums[i-1] > nums[i], so an equal element is never deleted — which means it is not a killer either, and must be popped like the smaller ones. Writing the pop as strict > is the bug this problem is built to catch:

text
nums = [5,3,4,4,7]
pop on >=  ->  dp = [0,1,2,3,0]  ->  3   ✓
pop on >   ->  dp = [0,1,2,1,0]  ->  2   ✗   (idx 3 wrongly adopts idx 2 as its killer)

Direction flips the operator, and this is the part worth memorising:

Scan Pop condition What the pops mean
Left → right nums[i] >= nums[stack[-1]] (non-strict) things that cannot kill me — smaller and equal
Right → left nums[i] > nums[stack[-1]] (strict) things that I eat — strictly smaller only

Pattern (Python)

python
# python
# LC 2289 - Steps to Make Array Non-decreasing
# IDEA: monotonic DECREASING stack of indices; dp[i] = round in which nums[i] dies.
#       A popped element hands its dp up: dp[i] = max(popped dp) + 1
# time = O(n), space = O(n)
def totalSteps(nums):
    n = len(nums)
    dp = [0] * n          # dp[i] = round nums[i] is removed (0 = never)
    stack = []            # indices, values monotonically DECREASING
    res = 0

    for i in range(n):
        cur = 0
        # NOTE !!! `>=` — an equal element is never deleted, so it is not a killer
        while stack and nums[i] >= nums[stack[-1]]:
            cur = max(cur, dp[stack.pop()])
        if stack:                       # a strictly greater element on the left = my killer
            dp[i] = cur + 1
            res = max(res, dp[i])
        stack.append(i)                 # else dp[i] stays 0: never removed

    return res

Same dp scanned right → left — each element computes its answer from what it eats, so the “is there a bigger element on my left?” branch disappears:

python
# python
# LC 2289 - variant: right -> left, steps = max(steps + 1, dp[j])
# time = O(n), space = O(n)
def totalSteps_rtl(nums):
    n = len(nums)
    dp = [0] * n
    stack, res = [], 0
    for i in range(n - 1, -1, -1):
        steps = 0
        while stack and nums[i] > nums[stack[-1]]:   # strict: only what I eat
            # one more round than eaten so far, but j may itself be dying until dp[j]
            steps = max(steps + 1, dp[stack.pop()])
        dp[i] = steps
        res = max(res, steps)
        stack.append(i)
    return res

Pattern (Java)

java
// java
// LC 2289 - Steps to Make Array Non-decreasing
// IDEA: decreasing stack of indices; dp[i] = round nums[i] is removed.
//       dp[i] = max(dp of popped) + 1 when a strictly greater element remains on the left
// time = O(N), space = O(N)
public int totalSteps(int[] nums) {
    int n = nums.length, res = 0;
    int[] dp = new int[n];                          // dp[i] = round nums[i] dies (0 = never)
    Deque<Integer> stack = new ArrayDeque<>();      // indices, values decreasing

    for (int i = 0; i < n; i++) {
        int cur = 0;
        while (!stack.isEmpty() && nums[i] >= nums[stack.peek()]) {
            cur = Math.max(cur, dp[stack.pop()]);   // inherit the deadline
        }
        if (!stack.isEmpty()) {
            dp[i] = cur + 1;
            res = Math.max(res, dp[i]);
        }
        stack.push(i);
    }
    return res;
}

A (value, steps) pair stack works identically and drops the dp array — push (num, cur) instead of the index, pop on stack[-1][0] <= num; that is the form traced above. Use it when the indices are not needed for anything else.

Similar Problems

Problem LC # Key Difference
Steps to Make Array Non-decreasing 2289 Popped element’s dp is inherited: dp[i] = max(popped dp) + 1
Online Stock Span 901 Same “carry a value through the pops”, but the aggregate is a sum of popped spans, not a max+1
Car Fleet II 1776 Right-to-left stack where each car’s collision time is derived from the popped cars — the same chaining, with a real-valued dp (§2-10)
Car Fleet 853 A count of survivors, not when anyone dies — so its stack never pops and two variables suffice (§2-10)
Count Robot Groups 4045 The same greedy as 853 with no finish line, so there is no arrival time to compare — speeds instead (§2-10)
Asteroid Collision 735 Same “stronger element eats weaker” simulation; the answer is the survivors, so no dp is carried
Minimum Cost Tree From Leaf Values 1130 Pop while smaller and aggregate a cost at each pop instead of a round number
Largest Rectangle in Histogram 84 Baseline contrast: the pop computes a final value (area) that nothing inherits

2-19) Classic Stack Problems Worth Knowing (non-monotonic)

These use a plain stack (no monotonic invariant) but show up constantly alongside the patterns above.

Problem LC # Key Technique Difficulty
Simplify Path 71 Split on /; push components, .. pops, ./empty skipped Medium
Backspace String Compare 844 Stack per string, or two pointers from the back for O(1) space Easy
Remove All Adjacent Duplicates In String 1047 Push char, pop when equal to top Easy
Remove All Adjacent Duplicates in String II 1209 Stack of (char, count), pop when count hits k Medium
Flatten Nested List Iterator 341 Stack of iterators/lists; flatten lazily in hasNext() Medium
Binary Search Tree Iterator 173 Controlled iterative inorder — stack of left spine Medium
Maximum Frequency Stack 895 freq map + map from frequency → stack of values Hard
Baseball Game 682 Straight stack simulation of +, D, C Easy