Stacks and Queues · Stacks and Queues Fundamentals

Auxiliary Stack Tracking

Maintaining a second stack alongside the main one, storing at each position the running minimum (or other aggregate) of everything below and including it - so that aggregate is always available at the top in O(1), even after arbitrary pops.

  • 5 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: a stack of stacks by frequency
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

A stack should also report its current minimum in O(1). Scanning is O(n). A single "minimum so far" variable breaks as soon as the minimum is popped - there's no way to know what the minimum was before it arrived. The fix is to remember the answer for every level of the stack.

This skill covers keeping a second stack of answers in step with the main one: the minimum at every level, so push, pop and getMin are all O(1). It also covers the related frequency stack, a stack per count, which pops the most frequent and most recent value in O(1).

We'll go from simple to more technical:

  1. The pattern: a min stack with a parallel stack of running minimums.
  2. A second flavor: a stack of stacks by frequency (Maximum Frequency Stack).
  3. Where per-level state shows up (undo histories, evaluators, frequency-aware caches).
  4. What breaks it: drifting stacks, < versus <=, one running variable, encoding overflow.

The approach

Java

Deque<Integer> stack = new ArrayDeque<>(), mins = new ArrayDeque<>();

void push(int val) {
    stack.push(val);
    mins.push(mins.isEmpty() ? val : Math.min(val, mins.peek()));   // minimum of everything up to here
}
void pop()   { stack.pop(); mins.pop(); }       // always together
int top()    { return stack.peek(); }
int getMin() { return mins.peek(); }

Python

stack, mins = [], []


def push(val: int) -> None:
    stack.append(val)
    mins.append(val if not mins else min(val, mins[-1]))   # minimum of everything up to here


def pop() -> None:
    stack.pop()
    mins.pop()                                    # always together


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


def get_min() -> int:
    return mins[-1]

mins has one entry per level: the minimum of the stack from the bottom up to that level. Popping both together "undoes" the minimum along with the value, so every operation is O(1). A common space-saving variant pushes to mins only when the new value is <= the current minimum, and pops mins when the popped value equals its top - the comparison must be <=: with <, pushing 0, 1, 0 and popping once loses the minimum 0 that's still in the stack.

A second flavor: a stack of stacks by frequency

"Pop the most frequent value; on ties, the one pushed most recently." Keep each value's count, and for each count a stack of the values that reached it:

Java

Map<Integer, Integer> freq = new HashMap<>();
Map<Integer, Deque<Integer>> byFreq = new HashMap<>();
int maxFreq = 0;

void push(int val) {
    int f = freq.merge(val, 1, Integer::sum);
    maxFreq = Math.max(maxFreq, f);
    byFreq.computeIfAbsent(f, x -> new ArrayDeque<>()).push(val);
}
int pop() {
    Deque<Integer> top = byFreq.get(maxFreq);
    int val = top.pop();                        // most frequent, most recent among them
    freq.merge(val, -1, Integer::sum);
    if (top.isEmpty()) maxFreq--;
    return val;
}

Python

from collections import defaultdict

freq = defaultdict(int)
by_freq = defaultdict(list)                       # count -> values that reached it, most recent last
max_freq = 0


def push(val: int) -> None:
    global max_freq
    freq[val] += 1
    max_freq = max(max_freq, freq[val])
    by_freq[freq[val]].append(val)


def pop() -> int:
    global max_freq
    top = by_freq[max_freq]
    val = top.pop()                               # most frequent, most recent among them
    freq[val] -= 1
    if not top:
        max_freq -= 1
    return val

A value pushed three times sits in the stacks for counts 1, 2 and 3; popping it from count 3 leaves its earlier copies where they belong. Each stack is ordered by recency, so ties need no extra comparison, and maxFreq drops by exactly one when its stack empties. All O(1).

Where you'll see it

Undo stacks that must report aggregate state (the lowest price in the current edit history, the current indentation depth), expression evaluators that track the running maximum, and caches that evict the most frequently used recent item all keep per-level state beside the main structure.

Which tool for which job?

You needUse
The minimum or maximum of a stack in O(1)a parallel stack of running values
The same with less memory on typical datapush to the side stack only on a new minimum (<=)
The most frequent value, ties by recencya map from count to a stack of values
Add to the bottom k values in O(1)a lazy pending-increment array
A sum that survives popsone running variable (sums can be undone)
The minimum of a queuetwo min-stacks

Common mistakes

MistakeWhat happensDo this instead
Pushing to mins sometimes but popping it alwaysthe stacks drift apartkeep them in step, or pop mins only on a match
< in the space-saving variantpushing 0, 1, 0 and popping once loses the 0<=
One running-minimum variableit can't be restored after a popa stack of minimums
2 * val - min in an intwrong minimum near the int limitsa long
Recomputing frequencies on every popO(n) per popper-count stacks and maxFreq

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why can't a single min variable support pop?

Show answer

When the current minimum is popped, the variable must go back to the minimum before it arrived - information a single variable doesn't keep. A second stack remembers the minimum at every level.

2. In the space-saving min stack, why must a new value be pushed to mins when it is <= (not <) the current minimum?

Show answer

Pops remove from mins when the popped value equals its top. If two equal minimums are in the stack but only one is recorded, popping one of them removes the record and loses the minimum that's still present.

3. How does the frequency stack break ties by recency without comparing anything?

Show answer

Values that reached the same count are kept in a stack for that count, in push order. Popping that stack returns the most recent one automatically.

Key takeaways

  • Store the aggregate for every level in a parallel stack and pop both together.
  • In the space-saving variant, use <= so repeated minimums survive.
  • For "most frequent, most recent": a map from count to a stack of values, plus maxFreq.
  • Encoded single-stack tricks need a long.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Stack min or max, O(1)parallel stack
Less memory, typical datapush only new minimums (<=)
Most frequent, then most recentcount -> stack, plus maxFreq
Increment the bottom kpending increments
Running sumone variable

Snippets worth knowing by heart

Java

void push(int v) { stack.push(v); mins.push(mins.isEmpty() ? v : Math.min(v, mins.peek())); }
void pop()       { stack.pop(); mins.pop(); }

Python

def pop(self) -> int:                      # increment-bottom-k stack
    add = self.inc.pop()
    if self.inc:
        self.inc[-1] += add                # pass the pending amount down
    return self.a.pop() + add

The traps

  • Drifting stacks: pushing to mins only sometimes but popping it always breaks the pairing. Pop mins only on a match.
  • < instead of <=: push 0, 1, 0, pop once, and the minimum 0 is lost. Push on equal values too.
  • One running minimum: a single variable can't be restored after a pop. Keep a stack of minimums.
  • int encoding: 2 * val - min overflows near the int limits. Do the arithmetic in a long.

Staff-level points, one line each

  • 100,000 random pushes: the space-saving variant kept 9 entries; decreasing pushes kept 100,000.
  • Invertible aggregates (sum, xor) need one variable; min, max and gcd need a stack.
  • The int encoding gave -2147483648 instead of -5; the long one was right.
  • Increment-bottom-k with pending adds matched brute force on 300 runs.
  • The frequency stack matched a recount-everything brute force on 300 runs.

Say it in an interview

  • "I store the minimum for every level, so popping restores the old minimum for free."
  • "Min has no inverse, which is why one variable isn't enough."
  • "For frequency, a stack per count gives most frequent and most recent in O(1)."
  • "Increments of the bottom k can be lazy, applied as elements pop."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Auxiliary Stack Tracking free

Min Stack

MediumProCoding problemJava · Python

Build a stack that, besides the usual push, pop and top, can tell you its smallest value at any moment. All four operations must take O(1) time.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Maximum Frequency Stack

HardProCoding problemJava · Python

Design a stack where pop() takes out the value that occurs most often among the values currently inside it. When several values tie, it takes the one pushed most recently.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Auxiliary Stack Tracking free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime