Stack

Hashing, Stacks & QueuesPriority 5 of 5 — Must know — expect it in almost every loopMust know Updated Oct 9, 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 — LIFO fundamentals and the canonical stack templates: bracket matching, min-stack, the short monotonic-stack form, explicit-stack traversal and the scope/context ledger. See also: stack_expression_parsing.md — calculators, decode-string and postfix evaluation, the whole expression-parsing family; stack_examples.md — the worked-solution archive behind these templates; monotonic_stack.md — next-greater / previous-smaller / span problems in depth; queue.md — the FIFO counterpart; iterator.md — stack-backed iterators.

LeetCode Problem Lists

Time Complexity

Data structure Search Insert Delete Min/Max
Stack O(n) O(1) O(1) O(n)

Insert = push, Delete = pop, peek — all at the top, all O(1). Min/Max can be made O(1) with an auxiliary min/max-stack (monotonic_stack.md). Space is O(n).

Overview

Stack is a data structure with Last-In-First-Out (LIFO) property. Each operation adds/removes from the top of the stack.

Key Properties

  • Complexity: see the Time Complexity table above
  • Core Principle: Last element added is the first one removed
  • Use Case: Problems involving order reversal, pattern matching, or maintaining context

References

Problem Categories

Ten shapes cover almost every stack question. The Where column says which template below owns the code, or which sheet the worked solutions moved to.

Category What the stack holds LC Where
Bracket / nesting validation the openers still owed a closer 20, 921, 1541, 1614 Template 2
Bracket repair / measurement indices of unmatched brackets 1249, 32, 856 Template 2 § LC 1249, stack_examples.md
Monotonic — next greater / smaller elements still waiting for an answer 496, 503, 739, 84, 907, 2104 Template 3, monotonic_stack.md
Monotonic — greedy removal the best prefix built so far 402, 316, 1081, 1673 stack_examples.md
Monotonic — span accumulation [value, span] pairs, streaming 901, 735 stack_examples.md
Stack with [element, count] pairs a run-length-compressed prefix 1047, 1209, 1544 stack_examples.md
O(1) aggregate on a stack one min / max / delta state per layer 155, 716, 1381, 895 Template 4
Expression parsing operands / deferred terms / open scopes 224, 227, 772, 394, 150, 682 stack_expression_parsing.md
Scope / context ledger the enclosing context, keyed by depth 388, 636, 591, 71 Template 6
Order reversal / paused traversal work not yet done 144, 145, 173, 341, 445 Template 5

Stack Variants Worth Knowing

  • Single stack
  • Build queue via stack
    • LC 232 (use 2 stack)
  • Build stack via queue
  • Stack with Pair (char, count)
    • Store [element, count] pairs instead of raw elements
    • LC 1047 (k=2 special case, simple pop)
    • LC 1209 (k consecutive duplicates removal)
    • LC 1544 (Make The String Great)
    • LC 394 (Decode String, stack with count for repetition)
    • LC 726 (Number of Atoms)

Templates & Algorithms

Template Comparison Table

Template Stack element Loop shape Complexity When to Use
1 — Basic operations anything — O(1) per op push / pop / peek idioms
2 — Bracket matching opener chars one pass, pop on closer O(n) / O(n) validate nesting, >1 bracket type
3 — Monotonic stack (value, index) while inside for O(n) / O(n) next greater / smaller / span
4 — Min stack value + one aggregate state per layer — O(1) per op O(1) getMin() / getMax(); prefix aggregates, path state
5 — Explicit stack pending nodes while stack O(n) / O(h) iterative traversal, order reversal
6 — Scope ledger enclosing context per depth one pass, trim to depth O(n) / O(depth) indented input, start/end events

Template 1: Basic Stack Operations

Stack push (insert):

java
// Java
Stack<Integer> stack = new Stack<>();
stack.push(element);  // O(1)
python
# Python
stack = []
stack.append(element)  # O(1)

Stack pop (remove top):

java
// Java
int top = stack.pop();  // O(1), throws if empty
python
# Python
top = stack.pop()  # O(1), raises if empty

Stack peek (view top):

java
// Java
int top = stack.peek();  // O(1), throws if empty
if (!stack.isEmpty()) {
    top = stack.peek();
}
python
# Python
top = stack[-1]  # O(1), raises if empty
if stack:
    top = stack[-1]

Template 2: Bracket Matching — LC 20 Priority 5 of 5 — Must know — expect it in almost every loop

The single most-asked stack pattern. Push openers, and on a closer check that the top is its partner. A stack is required (not a counter) as soon as there is more than one bracket type, because order matters: ([)] is invalid.

text
Core Idea:
  - Opener  -> push it (we owe a matching closer)
  - Closer  -> stack must be non-empty AND top must be the matching opener
               (else fail fast)
  - End     -> stack must be EMPTY (no unmatched openers left)

When to Use:
  - Validate / repair / measure balanced sequences
  - Any "nesting must be well-formed" check (brackets, tags, expressions)

Counter vs Stack (interview discriminator):
  - ONE bracket type only  -> a running balance counter is enough, O(1) space
                              (LC 921, LC 1541, LC 1614)
  - MULTIPLE bracket types -> MUST use a stack (LC 20)
  - Need the POSITION of the offending bracket -> stack of INDICES
                              (LC 1249, LC 32)

Similar LC:
  - LC 20    Valid Parentheses                        (base template)
  - LC 1249  Minimum Remove to Make Valid Parentheses (stack of indices -> delete;
                                                    also two counter scans, no stack)
  - LC 921   Minimum Add to Make Parentheses Valid    (balance counter)
  - LC 32    Longest Valid Parentheses                (index stack + `-1` base;
                                                    also 1D DP, or two counter scans for O(1) space)
  - LC 856   Score of Parentheses                     (stack of partial scores)
  - LC 1541  Minimum Insertions to Balance a Parentheses String  ( `(` needs `))` )
  - LC 1614  Maximum Nesting Depth of the Parentheses (max depth = max balance)
java
// java
// LC 20 - Valid Parentheses
// IDEA: STACK — push openers, pop-and-verify on closers, stack must end empty
// time = O(n), space = O(n)
public boolean isValid(String s) {

    // closer -> its matching opener
    Map<Character, Character> pairs = new HashMap<>();
    pairs.put(')', '(');
    pairs.put(']', '[');
    pairs.put('}', '{');

    Deque<Character> st = new ArrayDeque<>();

    for (char c : s.toCharArray()) {
        if (pairs.containsKey(c)) {
            /**
             *  NOTE !!!  a closer needs BOTH checks:
             *   1) stack NOT empty  (e.g. ")" alone)
             *   2) top is the matching opener (e.g. "(]" must fail)
             *
             *  NOTE !!! unbox to `char` before comparing —
             *  comparing two Character objects with `!=` compares REFERENCES.
             */
            if (st.isEmpty()) {
                return false;
            }
            char top = st.pop();
            if (top != pairs.get(c)) {
                return false;
            }
        } else {
            st.push(c);
        }
    }

    /** NOTE !!! leftover openers => invalid (e.g. "(((") */
    return st.isEmpty();
}
python
# python
# LC 20 - Valid Parentheses
# IDEA: STACK — push openers, pop-and-verify on closers, stack must end empty
# time = O(n), space = O(n)
class Solution(object):
    def isValid(self, s):
        pairs = {')': '(', ']': '[', '}': '{'}
        stack = []
        for c in s:
            # closer
            if c in pairs:
                # NOTE !!! empty stack OR wrong partner -> invalid
                if not stack or stack.pop() != pairs[c]:
                    return False
            # opener
            else:
                stack.append(c)
        # NOTE !!! leftover openers -> invalid
        return not stack

The bracket family — four twists on the same template (worked solutions in stack_examples.md):

LC Twist Stack holds
1249 repair, not just validate — below indices of unmatched ( — or no stack: two counter scans, one per direction
921 one bracket type only, so a counter suffices — O(1) space nothing (the stack degenerates to its size)
32 length of the longest valid run indices plus a -1 base sentinel
856 fold a score out of the nesting the partial result of each depth

Bracket Repair Variant — push the index, not the char (LC 1249) Priority 4 of 5 — High value — a gap here costs you rounds

What changed from LC 20: LC 20 only answers yes / no, so the stack can hold the opener characters and forget where they were. LC 1249 must erase the offenders, so it needs their positions — push the index of each (. There is only one bracket type, so nothing has to be compared on a pop: any open ( matches any ).

LC 20 — validate LC 1249 — repair
Stack holds opener chars indices of unmatched (
) with an empty stack return False mark index i for removal, keep scanning
) with a non-empty stack pop, check it is the partner pop — the pair is kept
End of scan stack must be empty every index left on the stack is an unmatched ( → remove it too
Output a boolean s minus the marked positions

Why it is the minimum. A ) with no ( open before it can never be matched by anything to its right, and an ( still on the stack at the end has no ) after it. Each removal is forced, and everything else pairs up, so nothing is removed that did not have to be.

python
# python
# LC 1249 - Minimum Remove to Make Valid Parentheses
# IDEA: STACK OF '(' INDICES — blank out unmatched ')' on sight, unmatched '(' after the scan
# time = O(n), space = O(n)
class Solution(object):
    def minRemoveToMakeValid(self, s):
        chars = list(s)      # mutable copy, so a position can be blanked in O(1)
        open_idx = []        # indices of '(' still waiting for a ')'

        for i in range(len(chars)):
            if chars[i] == '(':
                open_idx.append(i)
            elif chars[i] == ')':
                if open_idx:
                    open_idx.pop()     # matched -> keep both
                else:
                    chars[i] = ''      # NOTE !!! no '(' open before it -> can never match
            # letters never affect the balance

        # NOTE !!! the second flush: what is left on the stack are unmatched '('
        for i in open_idx:
            chars[i] = ''

        return ''.join(chars)  # '' entries vanish in the join
text
s = "a)b(c)d"

i  ch   action                          open_idx   chars
0  a    letter                          []         a ) b ( c ) d
1  )    stack empty -> blank index 1    []         a _ b ( c ) d
2  b    letter                          []
3  (    push 3                          [3]
4  c    letter                          [3]
5  )    pop 3 (matched)                 []
6  d    letter                          []
end     nothing left to flush                      -> "ab(c)d"

s = "))(("   ->  ')' x2 blanked on sight, '(' x2 left on the stack and flushed  ->  ""

Pitfalls:

  • Forgetting the second flush. The scan alone only catches the surplus ); "(()" needs the leftover index removed after the loop.
  • No len(s) <= 1: return s shortcut. "(" and ")" are each one character and each invalid — the answer is "". The general loop already handles the short strings.
  • Do not delete inside the loop. s = s[:i] + s[i+1:] is O(n) per removal (O(n²) overall) and shifts every index already on the stack. Mark now, rebuild once.
  • A set of indices to remove works too (to_remove.update(open_idx), then keep i not in to_remove). It is the same algorithm with one more structure. Blanking the list copy is the leaner form.

The Java version and the stack-free two counter scans (left→right drops the surplus ), right→left drops the surplus ( — the follow-up an interviewer asks next) are in stack_examples.md § 13; the same mark-then-rebuild idea framed as a string technique is string.md Template 7.


Template 3: Monotonic Stack — Next Greater / Smaller — LC 739 Priority 4 of 5 — High value — a gap here costs you rounds

Key Idea: the stack holds the elements that are still waiting for an answer, kept in monotonic order. Flip the comparison to flip the direction — top < cur pops for next greater, top > cur pops for next smaller. Every element is pushed once and popped at most once, so a while nested in a for is still O(n).

This sheet keeps one short form. The full family (previous-smaller, spans, histogram, subarray min/max sums, and the greedy-removal variants) is monotonic_stack.md’s subject; the worked solutions are in stack_examples.md.

  • Store (value, index) — the index is what turns “found it” into a distance or a width.
python
# python
# LC 739, LC 503 - Find next `big number`
# ...
stack = [] # [[idx, val]]
for i, val in enumerate(tmp):
    while stack and stack[-1][1] < val:
        _idx, _val = stack.pop(-1)
        res[tmp[_idx]] = i - _idx
    stack.append([i, val]) 
# ...
java
// java
// LC 239
// LC 496
// ...

// Traverse the array from right to left
for (int i = 0; i < n; i++) {
    // Maintain a decreasing monotonic stack
    /** NOTE !!! below */
    while (!stack.isEmpty() && nums[stack.peek()] <= nums[i]) {
        stack.pop();  // Pop elements from the stack that are smaller or equal to the current element
    }
    
    // If stack is not empty, the next greater element is at the top of the stack
    if (!stack.isEmpty()) {
        result[i] = nums[stack.peek()];
    }
    
    // Push the current element's index onto the stack
    stack.push(i);
}

// ...
Want Pop while Read the answer LC
next greater element / warmer day top < cur when cur pops top 496, 503, 739
next smaller element, or left+right bounds top > cur when cur pops top 84, 907, 2104
span back to the last bigger value top <= cur, accumulating the popped spans the accumulated count 901
circular array same, over nums * 2 with idx % n same 503

Template 4: Min Stack — O(1) getMin — LC 155 Priority 4 of 5 — High value — a gap here costs you rounds

Pattern: one aggregate per layer — min_values[i] == min(stack[0..i])

The stack you are asked for is the ordinary one. The trick is a second array that mirrors it layer for layer, holding not the elements but the answer to the query for everything at or below that layer. For LC 155 the query is “minimum”, so min_values = 每一層 stack 對應的 minimum. This is the design-problem face of a prefix aggregate, and the same idea shows up far outside stacks — see the classics table at the end of this template.

The general form — one aggregate per layer

text
Invariant:  min_values[i] = min(stack[0], stack[1], ..., stack[i])
            -> min_values is NOT the elements sorted; it is a STATE per layer

push(v):    min_values.append(min(v, min_values[-1]))   # new layer, new state
pop():      min_values.pop()                             # the popped layer's state goes with it
getMin():   min_values[-1]                               # the top layer's state IS the answer

push -2, 0, -3        then pop()
  stack      = [-2,  0, -3]      stack      = [-2,  0]
  min_values = [-2, -2, -3]      min_values = [-2, -2]   <- getMin() = -2, nothing recomputed
                  ^    ^    ^
                  |    |    min of all three
                  |    min of the first two (0 did not beat -2)
                  min of the first one

Three consequences fall straight out of the invariant:

  • The two arrays are always the same length — every push appends to both, every pop removes from both. No if in pop(), no comparing the popped value against the min: min_values[-1] is a state that belongs to the layer, not a copy of an element, so popping the layer is the whole job. That is why V0 below does not need the stack.pop() == mins[-1] check that the space-saving variant needs.
  • Duplicates are free. push(0); push(0); pop() leaves min_values = [0], because each 0 got its own layer. The variant that stores only new minima has to write <= to get this right — the classic LC 155 bug (see monotonic_stack.md § 2-16).
  • Any aggregate that is a function of the prefix works the same way — max, a running sum, a running GCD, a pending delta (LC 1381), a frequency count (LC 895). Replace min(...) in push and nothing else changes.

Why O(1) — the state was computed at push time

getMin() reads one array cell. There is no scan, because the work was moved to push, and push only has to combine two numbers: the new value and the answer for the layer below. That is the whole argument — each operation touches the top of two arrays and nothing else.

The point of keeping every layer’s state instead of one self.min variable is pop(). A single variable can be lowered when a smaller value arrives, but cannot be raised back when that value leaves — the earlier minimum was overwritten. The array remembers it, because it was never overwritten; it was one layer down the whole time.

Approach push pop getMin Why it falls short
one self.min variable O(1) O(n) O(1) after popping the min, the previous min has to be rescanned
heap (heapq) O(log n) O(log n) with lazy deletion O(1) pays a log for an ordering the stack never uses
{value: count} map O(1) O(1) O(n) min(counts) is a scan (this is min-stack.py’s V0-1)
per-layer min_values O(1) O(1) O(1) the answer for the current top was fixed when the top was pushed
python
# LC 155. Min Stack
# V0
# IDEA: 2 ARRAYS — stack holds the elements, min_values holds each layer's minimum
# time = O(1) per operation, space = O(n)
class MinStack(object):

    def __init__(self):
        self.stack = []
        # min_values[i] == min(stack[0..i]) — a STATE per layer, not the elements sorted
        self.min_values = []

    def push(self, val):
        self.stack.append(val)
        if not self.min_values:
            self.min_values.append(val)
        else:
            self.min_values.append(min(val, self.min_values[-1]))

    def pop(self):
        # the popped layer's state leaves with it — no comparison needed
        self.min_values.pop()
        return self.stack.pop()

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

    def getMin(self):
        # the top layer's state IS the current minimum — O(1)
        return self.min_values[-1]
python
# V1: the same invariant in one stack of (value, min_so_far) tuples
# time = O(1) per operation, space = O(n)
class MinStack(object):

    def __init__(self):
        self.stack = []

    def push(self, x):
        if not self.stack:
            self.stack.append((x, x))
        else:
            self.stack.append((x, min(x, self.stack[-1][1])))

    def pop(self):
        self.stack.pop()

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

    def getMin(self):
        return self.stack[-1][1]

The same idea beyond stacks — the classics Priority 4 of 5 — High value — a gap here costs you rounds

The invariant needs a container that only changes at one end, so that “the state one layer down” is still valid when the top goes away. Four things in the LC catalogue behave like that. Read the table by the first column: find what is playing the stack in your problem, and the aggregate you need to carry per layer follows.

What plays the stack Per-layer state Query it answers Classic LC
an explicit stack min / max so far getMin() / getMax() in O(1) 155 Min Stack, 716 Max Stack (peekMax)
a pending delta for everything below bulk increment(k, val) in O(1) 1381 Design a Stack With Increment Operation
one stack per frequency level pop the most frequent in O(1) 895 Maximum Frequency Stack
two stacks forming a queue (LC 232) min / max so far in each stack window min / max in amortised O(1), without a monotonic deque 239 Sliding Window Maximum, 1438 Longest Continuous Subarray With Absolute Diff ≤ Limit
an array that only grows — index = layer prefix min / max / sum / product “best value at or before i” for every i 121 Best Time to Buy and Sell Stock, 2016 Maximum Difference Between Increasing Elements, 42 Trapping Rain Water, 238 Product of Array Except Self, 303 Range Sum Query, 769 Max Chunks To Make Sorted, 915 Partition Array into Disjoint Intervals, 1477 Find Two Non-overlapping Sub-arrays Each With Target Sum
the recursion stack — the root-to-node path (lo, hi) / max / prefix value along the path a per-node answer about its ancestors 1026 Maximum Difference Between Node and Ancestor, 98 Validate Binary Search Tree, 1448 Count Good Nodes in Binary Tree, 129 Sum Root to Leaf Numbers

Two rules decide how much of the state to keep:

  • Keep the whole array when a later query asks about a layer that is not the top. LC 42 needs left_max[i] for every i on the second pass; LC 238 needs every prefix product; LC 769 / 915 compare a prefix max against a suffix min at each split. One variable cannot answer for a layer it has already moved past.
  • Collapse it to one variable when only the top is ever queried and nothing pops. LC 121 asks “min so far” once per index and never goes back, so min_values is a single lo — the array’s last cell, kept and the rest thrown away. LC 155 cannot do this because pop() does go back.
python
# LC 239 (as a min queue) / LC 1438 - a queue built from two min stacks
# IDEA: a queue is two stacks (LC 232). Give each stack its per-layer min and the
#       queue's min is the smaller of the two tops — no monotonic deque needed.
#       Swap min -> max for LC 239 itself; run one of each for LC 1438.
# time = O(1) amortised per operation, space = O(n)
class MinQueue(object):

    def __init__(self):
        self.inbox, self.outbox = [], []      # each entry: (value, min_so_far)

    @staticmethod
    def _push(st, v):
        st.append((v, v if not st else min(v, st[-1][1])))

    def push(self, v):
        self._push(self.inbox, v)

    def pop(self):
        if not self.outbox:                   # refill: each element moves once
            while self.inbox:
                self._push(self.outbox, self.inbox.pop()[0])
        return self.outbox.pop()[0]

    def getMin(self):
        return min(st[-1][1] for st in (self.inbox, self.outbox) if st)
python
# LC 1026 - Maximum Difference Between Node and Ancestor
# IDEA: the recursion stack IS the stack; (lo, hi) is that layer's aggregate over the
#       root-to-node path. Returning from the call pops it — no bookkeeping at all.
# time = O(n), space = O(h)
def maxAncestorDiff(root):
    def dfs(node, lo, hi):
        if not node:
            return hi - lo
        lo, hi = min(lo, node.val), max(hi, node.val)
        return max(dfs(node.left, lo, hi), dfs(node.right, lo, hi))
    return dfs(root, root.val, root.val)

Where the depth lives for each row: the LC 1381 delta trick in design_examples.md § 6, the deque form of LC 239 in monotonic_queue.md, prefix arrays in prefix_sum.md, LC 121 in stock_trading.md, and the (lo, hi) bounds pattern in bst_advanced.md.


Template 5: Explicit Stack — Iterative Traversal — LC 144, LC 145 Priority 4 of 5 — High value — a gap here costs you rounds

Key Idea: recursion’s call stack, made explicit. Push the work not yet done, pop it to do it. Preorder pushes right before left, because a stack flips whatever you feed it. Postorder is the cheapest trick in trees: run preorder as root → right → left, then reverse the output.

The paused form of the same stack — an iterator that must stop between elements — is LC 173 / LC 341 in stack_examples.md, and the wider family is iterator.md.

python
# python
# LC 144 - Binary Tree Preorder Traversal
# IDEA: EXPLICIT STACK — push RIGHT before LEFT so LEFT is popped first
# time = O(n), space = O(h)
class Solution(object):
    def preorderTraversal(self, root):
        if not root:
            return []
        res, stack = [], [root]
        while stack:
            node = stack.pop()
            res.append(node.val)
            # NOTE !!! right first -> left ends up on TOP
            if node.right:
                stack.append(node.right)
            if node.left:
                stack.append(node.left)
        return res


# LC 145 - Binary Tree Postorder Traversal
# IDEA: preorder variant (root -> RIGHT -> LEFT), then REVERSE => left-right-root
# time = O(n), space = O(h)
class Solution(object):
    def postorderTraversal(self, root):
        if not root:
            return []
        res, stack = [], [root]
        while stack:
            node = stack.pop()
            res.append(node.val)
            # NOTE !!! mirrored order compared with preorder
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right)
        return res[::-1]   # root-right-left  ->  left-right-root
java
// java
// LC 144 - Binary Tree Preorder Traversal
// IDEA: EXPLICIT STACK — push RIGHT before LEFT so LEFT is popped first
// time = O(n), space = O(h)
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) {
        return res;
    }
    Deque<TreeNode> st = new ArrayDeque<>();
    st.push(root);
    while (!st.isEmpty()) {
        TreeNode node = st.pop();
        res.add(node.val);
        /** NOTE !!! right pushed FIRST, so left is popped FIRST */
        if (node.right != null) {
            st.push(node.right);
        }
        if (node.left != null) {
            st.push(node.left);
        }
    }
    return res;
}

// LC 145 - Binary Tree Postorder Traversal
// IDEA: preorder with LEFT/RIGHT swapped (root-right-left), then REVERSE
// time = O(n), space = O(h)
public List<Integer> postorderTraversal(TreeNode root) {
    LinkedList<Integer> res = new LinkedList<>();
    if (root == null) {
        return res;
    }
    Deque<TreeNode> st = new ArrayDeque<>();
    st.push(root);
    while (!st.isEmpty()) {
        TreeNode node = st.pop();
        /** NOTE !!! addFirst == "append then reverse", done incrementally */
        res.addFirst(node.val);
        if (node.left != null) {
            st.push(node.left);
        }
        if (node.right != null) {
            st.push(node.right);
        }
    }
    return res;
}

Template 6: Scope / Context Ledger — LC 388, LC 636 Priority 5 of 5 — Must know — expect it in almost every loop

Key Idea: the stack does not hold characters — it holds the enclosing context (a path prefix, a running function, an open tag). Entering a scope pushes context, leaving it pops, and the answer is computed against stack[-1] / stack[depth] — the context you are currently inside.

This is the pattern behind the highest-frequency Google stack questions, and it is not bracket matching: the “brackets” are implicit (indentation depth, start/end log events).

text
Core Idea:
  - Stack index == NESTING DEPTH. stack[d] = accumulated context at depth d.
  - On entering depth d : trim the stack down to d, then push the new context
  - On leaving  a scope : pop, and hand the accumulated value back to the parent
  - The parent is ALWAYS stack[-1] — that is what makes it a stack problem

When to Use:
  - Indented / tab-delimited input   -> depth = number of leading tabs (LC 388)
  - start/end (or open/close) events -> the "running" item is the stack top (LC 636)
  - Any "who is my parent?" question during a single left-to-right scan

Similar LC:
  - LC 388  Longest Absolute File Path       (depth-indexed prefix lengths)
  - LC 636  Exclusive Time of Functions      (running function = stack top)
  - LC 591  Tag Validator                    (open-tag stack + scope rules)
  - LC 71   Simplify Path                    (stack_examples.md; ".." pops the parent dir)
java
// java
// LC 388 - Longest Absolute File Path
// IDEA: SCOPE STACK indexed by DEPTH — stack.get(d) = length of the path prefix at depth d
// time = O(n), space = O(depth)
public int lengthLongestPath(String input) {

    int res = 0;

    /**
     *  NOTE !!!
     *   stack.get(d) = length of "dir1/dir2/.../" for the current branch at depth d
     *   (already includes the trailing '/')
     *   -> index in the list IS the nesting depth
     */
    List<Integer> stack = new ArrayList<>();
    stack.add(0); // depth 0 has an empty prefix

    for (String line : input.split("\n")) {

        // depth = number of leading '\t'
        int depth = 0;
        while (depth < line.length() && line.charAt(depth) == '\t') {
            depth++;
        }
        String name = line.substring(depth);

        /** NOTE !!! we LEFT the previous deeper scopes -> pop back to `depth` */
        while (stack.size() > depth + 1) {
            stack.remove(stack.size() - 1);
        }

        if (name.contains(".")) {
            // a FILE never becomes a parent -> just measure it
            res = Math.max(res, stack.get(depth) + name.length());
        } else {
            // a DIRECTORY becomes the context of depth+1 (+1 for the '/')
            stack.add(stack.get(depth) + name.length() + 1);
        }
    }

    return res;
}
python
# python
# LC 388 - Longest Absolute File Path
# IDEA: SCOPE STACK indexed by DEPTH — stack[d] = length of the path prefix at depth d
# time = O(n), space = O(depth)
class Solution(object):
    def lengthLongestPath(self, input):
        res = 0
        stack = [0]   # stack[d] = prefix length at depth d (trailing '/' included)

        for line in input.split('\n'):
            name = line.lstrip('\t')
            depth = len(line) - len(name)   # number of leading tabs

            # NOTE !!! we left deeper scopes -> trim the stack back to this depth
            while len(stack) > depth + 1:
                stack.pop()

            if '.' in name:
                # file: measure, never push (a file has no children)
                res = max(res, stack[depth] + len(name))
            else:
                # dir: becomes the prefix for depth+1, +1 for the '/'
                stack.append(stack[depth] + len(name) + 1)

        return res

# Trace: "dir\n\tsubdir2\n\t\tfile.ext"
#   "dir"        d=0 -> stack = [0, 4]           ("dir/")
#   "subdir2"    d=1 -> stack = [0, 4, 12]       ("dir/subdir2/")
#   "file.ext"   d=2 -> res = 12 + 8 = 20

Variant — overwrite instead of pop (the form README files LC 388 under Hash_table). The trim loop is optional. The input is a pre-order listing, so a line at depth d reads only path_len[d], and the last write to that slot was made by its own parent — no line at depth ≤ d-1 sits between the two. Deeper stale slots are never read before they are rewritten, so a map (or array) indexed by depth replaces the stack. Same O(n); what it teaches is that the ledger is really the last write per depth.

java
// java
// LC 388 - Longest Absolute File Path
// IDEA: HASH MAP by DEPTH — pathLen[d] = prefix length for children at depth d; overwrite, never pop
// time = O(n), space = O(depth)
public int lengthLongestPath(String input) {
    int res = 0;
    Map<Integer, Integer> pathLen = new HashMap<>();
    pathLen.put(0, 0); // depth-0 entries have an empty prefix

    for (String line : input.split("\n")) {
        int depth = line.lastIndexOf('\t') + 1;   // tabs are leading and contiguous
        String name = line.substring(depth);

        if (name.contains(".")) {
            res = Math.max(res, pathLen.get(depth) + name.length());
        } else {
            // NOTE !!! overwrite the slot for depth+1 — any stale deeper slot is rewritten before it is read
            pathLen.put(depth + 1, pathLen.get(depth) + name.length() + 1);
        }
    }
    return res;
}
python
# python
# LC 388 - Longest Absolute File Path
# IDEA: HASH MAP by DEPTH — path_len[d] = prefix length for children at depth d; overwrite, never pop
# time = O(n), space = O(depth)
class Solution(object):
    def lengthLongestPath(self, input):
        res = 0
        path_len = {0: 0}   # depth-0 entries have an empty prefix

        for line in input.split('\n'):
            name = line.lstrip('\t')
            depth = len(line) - len(name)

            if '.' in name:
                res = max(res, path_len[depth] + len(name))
            else:
                # NOTE !!! overwrite the slot for depth+1 — any stale deeper slot is rewritten before it is read
                path_len[depth + 1] = path_len[depth] + len(name) + 1

        return res
java
// java
// LC 636 - Exclusive Time of Functions
// IDEA: SCOPE STACK of function ids — the RUNNING function is always the stack top
// time = O(n), space = O(n)
public int[] exclusiveTime(int n, List<String> logs) {

    int[] res = new int[n];
    Deque<Integer> stack = new ArrayDeque<>(); // ids of functions currently RUNNING
    int prev = 0;  // timestamp where the current "run slice" started

    for (String log : logs) {
        String[] p = log.split(":");       // {id, "start"|"end", timestamp}
        int id = Integer.parseInt(p[0]);
        int t = Integer.parseInt(p[2]);

        if (p[1].equals("start")) {
            /**
             *  NOTE !!!
             *   the caller (stack top) ran during [prev, t) -> credit it,
             *   then it gets PREEMPTED by the new callee
             */
            if (!stack.isEmpty()) {
                res[stack.peek()] += t - prev;
            }
            stack.push(id);
            prev = t;
        } else {
            /**
             *  NOTE !!!
             *   "end at t" is INCLUSIVE -> the slice is [prev, t], hence `+ 1`
             *   and the caller resumes at t + 1
             */
            res[stack.pop()] += t - prev + 1;
            prev = t + 1;
        }
    }

    return res;
}
python
# python
# LC 636 - Exclusive Time of Functions
# IDEA: SCOPE STACK of function ids — the RUNNING function is always the stack top
# time = O(n), space = O(n)
class Solution(object):
    def exclusiveTime(self, n, logs):
        res = [0] * n
        stack = []    # ids of functions currently RUNNING (top = executing now)
        prev = 0      # start of the current time slice

        for log in logs:
            fid, typ, t = log.split(':')
            fid, t = int(fid), int(t)

            if typ == 'start':
                # the caller ran on [prev, t) before being preempted
                if stack:
                    res[stack[-1]] += t - prev
                stack.append(fid)
                prev = t
            else:
                # 'end' timestamp is INCLUSIVE -> [prev, t] => + 1
                res[stack.pop()] += t - prev + 1
                prev = t + 1

        return res

LC 636 — what the stack and prev each hold. The stack is the call stack the problem describes: its top is the function on the CPU right now. prev is a single cursor over the timeline — the first time unit nobody has been credited for yet. Every log line closes the slice [prev, …] and credits it to whoever was on top, then moves the cursor.

text
n = 2, logs = ["0:start:0","0:start:2","0:end:5","1:start:6","1:end:6","0:end:7"]

log          credit (to stack top)        stack      prev   res
0:start:0    (stack empty)                [0]        0      [0, 0]
0:start:2    res[0] += 2 - 0     = 2      [0, 0]     2      [2, 0]   <- recursive call, same id
0:end:5      res[0] += 5 - 2 + 1 = 4      [0]        6      [6, 0]
1:start:6    res[0] += 6 - 6     = 0      [0, 1]     6      [6, 0]   <- zero-length slice, harmless
1:end:6      res[1] += 6 - 6 + 1 = 1      [0]        7      [6, 1]
0:end:7      res[0] += 7 - 7 + 1 = 1      []         8      [7, 1]   -> answer [7, 1]

Why + 1 only on end. start:t means the beginning of unit t, end:t means the end of unit t. So a start boundary sits at t and an end boundary sits at t + 1. Convert end:t to boundary t + 1 the moment you parse it and both branches become the same half-open [prev, boundary) subtraction — the form below. Same O(n); the reason to know it is that the off-by-one disappears instead of being remembered.

python
# python
# LC 636 - Exclusive Time of Functions
# IDEA: normalise every log to a half-open BOUNDARY (end:t -> t + 1), so one formula credits both
# time = O(n), space = O(n)
class Solution(object):
    def exclusiveTime(self, n, logs):
        res = [0] * n
        stack = []
        prev = 0      # boundary where the current slice began
        for log in logs:
            fid, typ, t = log.split(':')
            fid, t = int(fid), int(t)
            boundary = t if typ == 'start' else t + 1
            if stack:
                # the stack top ran on [prev, boundary)
                res[stack[-1]] += boundary - prev
            if typ == 'start':
                stack.append(fid)
            else:
                stack.pop()
            prev = boundary
        return res

Pitfalls specific to LC 636:

  • Credit before you push or pop. The slice that just ended belongs to the old top; pushing first hands the caller’s time to the callee.
  • One frame per call, not one entry per id. A function can call itself (example 2), so a {id: start_time} map overwrites the outer call. Two frames with the same id is correct.
  • prev is one global cursor, not a per-function start time. The alternative keeps [id, resume_time] frames and rewrites the caller’s resume_time = t + 1 after each pop — the same idea with the cursor stored on the frame, and one more line to forget.
  • A zero-length slice is fine. 1:start:6 right after prev = 6 credits 0; no special case.

Summary & Quick Reference

Decision Table — Which Stack Pattern?

Problem Type Pattern Key Idea Examples
Find next greater/smaller element Monotonic Stack Maintain increasing/decreasing order LC 496, 503, 739
Remove adjacent duplicates Stack with Pair [element, count] Track counts, pop when k reached LC 1047, 1209, 1544
Decode strings with brackets Stack with Count Use pairs for nested repetitions LC 394, 726
Arithmetic expressions Stack with Operators Handle precedence and evaluation LC 224, 227
Remove k digits for min number Greedy + Monotonic Pop larger digits when beneficial LC 402
Lexicographically smallest with duplicates Monotonic + Last Occurrence Greedy removal with “appears later” check LC 316, 1081
Streaming/online frequency Stack with Span Pairs Accumulate counts in pairs LC 901
FIFO from LIFO Two Stacks Use input/output stacks for queue LC 232
O(1) min / max alongside push / pop Per-layer aggregate min_values[i] = min(stack[0..i]), popped with its layer LC 155, 716, 1381
Balanced-bracket validation Bracket Matching Push openers, pop-and-verify on closers LC 20, 1249, 32
Nesting context (indent, start/end events) Scope / Context Ledger stack[depth] = the enclosing context LC 388, 636, 591
Reverse a forward-only sequence Push-all, then pop Popping yields reverse order LC 445, 234, 143
Iterative traversal / lazy iterator Explicit Stack Stack holds work not yet done LC 144, 145, 173, 341
Postfix / RPN evaluation Operand Stack Operator pops two, pushes the result LC 150, 682

How to read: Find your problem goal in the leftmost column, then use the pattern and examples as a starting point.

Pattern Complexity

Per-pattern cost. The per-operation cost of the structure itself is the Time Complexity table at the top.

Pattern Time Space Why
Bracket matching O(n) O(n), or O(1) with one bracket type one pass, each char pushed at most once
Monotonic stack O(n) O(n) each element pushed once, popped at most once
Greedy removal (k drops) O(n) O(n) same amortised argument; k bounds the pops
[element, count] pairs O(n) O(n) the stack is a run-length encoding of the prefix
Min stack O(1) per op O(n) one auxiliary entry per push
Explicit-stack traversal O(n) O(h) only the current root-to-node path is pending
Scope ledger O(n) O(depth) one entry per open scope
Expression parsing O(n) O(n) stack depth = nesting depth

Common Traps

  • Monotonic Stack: Critical pattern for problems involving “next greater/smaller” — check if the pattern requires increasing or decreasing order
  • Stack with Pair: For adjacent duplicate removal or nested counting problems, store [element, count] pairs
  • Greedy Removal: Some problems benefit from greedily removing elements while maintaining an invariant
  • Empty-stack check first: every pop() / peek() driven by a closer needs !stack.isEmpty() in front of it.
  • Character vs char in Java: != on two boxed Characters compares references — unbox before comparing.
  • Integer division truncates toward zero in these problems; Python’s // floors, so use int(a / b).
  • Push children in reverse when a stack must yield left-to-right order.

Where the Rest Lives

Looking for File
Calculators (LC 224 / 227 / 772), decode string (LC 394), postfix (LC 150) stack_expression_parsing.md
Worked solutions for the problems named above stack_examples.md
Next-greater / previous-smaller / histogram theory monotonic_stack.md
Iterator design (LC 173, 341, 284) iterator.md
FIFO, deque, monotonic queue queue.md, monotonic_queue.md