Medium

Min Stack

Keep a parallel stack of the minimum-so-far.

Approach

Alongside the main stack, push the current minimum at each step (the smaller of the new value and the previous min). Pop both together. getMin is then the top of the min-stack in O(1), with no scanning.

Time complexity

O(1) per operation

Space complexity

O(n)

Common mistake

Scanning the whole stack for the min on each getMin call — that's O(n) and defeats the purpose.

Interactive walkthrough — step through it below, free and without an account

Problem

Design a stack data structure that supports adding an integer, removing the top integer, reading the top integer, and retrieving the smallest integer currently stored. Every operation, including retrieving the minimum, should run in O(1) time.

Examples

Input: push(-2), push(0), push(-3), getMin(), pop(), getMin(), top(), pop(), getMin()

Output: [null, null, null, -3, null, -2, 0, null, -2]

The minimum becomes -3 after the third push. After removing -3, the previous minimum -2 is restored.

Input: push(5), push(2), push(4), getMin(), pop(), getMin()

Output: [null, null, null, 2, null, 2]

The value 4 is removed, but 2 remains the minimum.

Problem

Problem

Design a stack data structure that supports adding an integer, removing the top integer, reading the top integer, and retrieving the smallest integer currently stored. Every operation, including retrieving the minimum, should run in O(1) time.

Problem and Goal

Learning goal: Understand why an ordinary stack needs extra state to answer minimum queries in constant time.

Core idea A normal stack gives constant-time access to its top, but the minimum may be buried anywhere below it. If getMin() scans every stored value, one query costs O(n). The goal is to remember enough information during each push() so that the current minimum is always immediately available.

Algorithm plan

  • Store values in a main stack.
  • Store the minimum-so-far at the same depth in a second stack.
  • On push(val), compare val with the previous minimum and push the smaller value onto the minimum stack.
  • On pop(), remove one item from both stacks.
  • On top() or getMin(), read the top of the relevant stack.

Micro-example After pushing -2, 0, and -3, the main stack is [-2, 0, -3]. The minimum stack is [-2, -2, -3], so getMin() reads -3 directly.

Pitfalls

  • Do not scan the main stack inside getMin().
  • Pop from both stacks together so their depths remain aligned.
  • The minimum stack stores history, not just the single minimum value.

Recap

  • The required operations must all be O(1).
  • A parallel stack turns minimum lookup into a top operation.
  • Each stack position has a matching minimum-so-far position.

Problem and Goal

Every operation stays local

The stack must answer `getMin()` without inspecting older entries one by one.

Store the answer incrementally so `getMin()` is one top-of-stack read.

Key Idea: Minimum-So-Far

Learning goal: See how each entry in the auxiliary stack represents the minimum of a complete prefix of the main stack.

Core idea At depth i, the minimum stack stores the smallest value among the first i + 1 values in the main stack. When a new value arrives, the new minimum is either that value or the previous minimum. This is the same information a scan would compute, but it is computed once during push() and reused by every later getMin().

Definition

If the main stack receives val, the matching minimum-stack entry is min(val, previousMin).

Data structure model

StructureEntry at depth iRead by
Main stackThe actual valuetop()
Minimum stackMinimum of values through depth igetMin()

For the main stack [-2, 0, -3], the parallel stack is [-2, -2, -3]. Notice that the second -2 is intentionally repeated: it preserves the minimum after 0 is later removed.

Micro-example Before pushing 4, suppose the previous minimum is 2. The new minimum is min(4, 2) = 2, so both stacks receive a corresponding entry.

Pitfalls

  • Storing only a new record minimum loses information when that minimum is popped.
  • Forgetting repeated minima breaks restoration after a pop.
  • The two stacks must have equal lengths after every completed operation.

Recap

  • Each auxiliary entry answers a prefix minimum.
  • The new entry is min(new value, previous minimum).
  • Repeated values are useful and sometimes necessary.

Key Idea: Minimum-So-Far

Every main-stack entry has exactly one matching minimum-so-far entry.

Main Approach and Invariant

Learning goal: Translate the idea into precise operations and preserve the stack invariant.

Core idea The implementation uses two synchronized stacks: values and mins. For the first value, the minimum is the value itself. For every later push, compare against mins[-1]. Because pop() removes the newest value, removing the matching minimum entry automatically restores the minimum that was valid one level earlier.

Data structure model

OperationMain stack actionMinimum stack actionTime
push(val)Push valPush min(val, mins[-1])O(1)
pop()Remove topRemove topO(1)
top()Read topNoneO(1)
getMin()NoneRead topO(1)
Synchronized stack operations
  1. On the first push(val), push val onto both stacks.
  2. Otherwise compute currentMin = min(val, mins[-1]).
  3. Push val onto values and currentMin onto mins.
  4. For pop(), pop from both stacks.
  5. Return values[-1] for top() and mins[-1] for getMin().

Micro-example Push -2: mins = [-2]. Push 0: mins = [-2, -2]; push -3: mins = [-2, -2, -3].

Pitfalls

  • Calling min() over all of values inside getMin() defeats the design.
  • Popping only values leaves stale minimum data.
  • Empty-stack handling should follow the stated method preconditions.

Recap

  • Synchronization preserves the prefix-minimum invariant.
  • Every operation touches only stack tops.
  • The minimum is available in O(1) time.

Main Approach and Invariant

DesigngetMin()Extra spaceKey property
Parallel minimum stackO(1)O(n)Minimum-so-far is stored at the top
Scan main stackO(n)O(1)Recomputes the minimum for every query

End-to-End Walkthrough

Learning goal: Trace one complete sequence and observe both stacks change together.

Core idea We will use the sequence push(-2), push(0), push(-3), getMin(), pop(), getMin(), top(), pop(), getMin(). Each push creates a pair: the actual value and the minimum valid through that depth. Each pop removes the pair, which exposes the earlier minimum without recomputation.

Algorithm plan

  • Start with both stacks empty.
  • Process each operation from left to right.
  • For push, append a value/minimum pair.
  • For getMin, inspect the minimum component of the top pair.
  • For pop, remove the top pair.

Micro-example After three pushes, the pairs are (-2, -2), (0, -2), (-3, -3). After popping -3, the pair (0, -2) becomes visible, so the minimum immediately returns to -2.

Why restoration is automatic

The minimum recorded beneath a popped value was the minimum before that value arrived. Revealing it restores the correct answer.

Pitfalls

  • Track the minimum after each push, not only when a new record minimum appears.
  • A getMin() does not modify either stack.
  • The final minimum after removing 0 is still -2, not an empty result.

Recap

  • The trace follows the problem example exactly.
  • The minimum changes on pushes and is restored by paired pops.
  • Queries are reads, not scans or mutations.

End-to-End Walkthrough

A synchronized stack trace for the concrete example.

Stack simulation
1 / 9
push(-2): pair the value with minimum-so-far -2.

Stack (top → bottom)

{"value":-2,"min":-2}

Incoming

{"value":-2,"min":-2}

Action

push
The first value initializes both stacks.Each entry records the minimum through its depth.
1 / 9

Pitfalls and Edge Cases

Learning goal: Identify the implementation mistakes that break either correctness or the required complexity.

Core idea The auxiliary stack is valuable only if it is updated exactly when the main stack changes. The most common wrong approach is to call a minimum function that scans every element during getMin(). That produces the right answer, but it violates the central performance requirement.

Algorithm plan

  • For every push, append one value and one minimum-so-far entry.
  • For every pop, remove one entry from each stack.
  • For getMin, return only the auxiliary stack's top.
  • Test a decreasing sequence, an increasing sequence, and duplicate minima.

Micro-example For push(2), push(2), pop(), the minimum must still be 2. The minimum stack should be [2, 2], not merely [2], so one pop does not erase the remaining minimum.

Common pitfall

Scanning the whole stack for the minimum on each getMin() call costs O(n). It defeats the purpose of the parallel stack.

Pitfalls

  • Do not store only record-breaking minima; duplicates must be represented.
  • Do not let the two stacks drift to different lengths.
  • Do not confuse top() with getMin() when the top value is not the smallest.
  • Check negative values and repeated minimum values.

Recap

  • Correctness depends on synchronized push/pop operations.
  • Duplicate minima need duplicate auxiliary entries.
  • The scan-based solution is explicitly too slow for getMin().

Pitfalls and Edge Cases

Why is scanning every element of the main stack inside `getMin()` incorrect for this problem's target?

Complexity and Wrap-Up

Learning goal: Justify the final complexity and recognize the reusable design pattern.

Core idea Each operation performs a constant number of stack actions and comparisons. The auxiliary stack stores one minimum entry per main-stack entry, so the extra memory grows linearly with the number of stored values. This trades O(n) space for O(1) minimum queries.

Data structure model

ResourceCostReason
push()O(1)One comparison and two pushes
pop()O(1)One pop from each stack
top()O(1)Read the main-stack top
getMin()O(1)Read the minimum-stack top
SpaceO(n)One auxiliary entry per value

Reusable pattern

When a query asks for an aggregate over a changing sequence, store the aggregate-so-far alongside each new element if it can be updated locally.

Micro-example With n values, both stacks contain at most n entries. Even after many getMin() calls, no additional scan is performed.

Pitfalls

  • Do not report O(1) space: the minimum stack grows with n.
  • Do not claim only getMin() is constant time; all four operations are.
  • Do not replace the auxiliary history with one global minimum unless you also track restoration information.

Recap

  • Time is O(1) per operation.
  • Space is O(n).
  • The parallel stack converts a global-looking query into a local read.

Complexity and Wrap-Up

Final cost breakdown for the two-stack design.

Complexity analysis
Time
O(1)
Space
O(n)
Where the time goes
  • push()Compare once and push onto both stacks.dominantO(1)
  • pop()Pop once from each synchronized stack.dominantO(1)
  • top()Read the main-stack top.dominantO(1)
  • getMin()Read the minimum-stack top; no scan.dominantO(1)
  • Stored minimum historyOne auxiliary minimum per main-stack entry.O(n)
How it grows
input size n →opsO(n²)O(n log n)O(n)O(log n)O(1)
Roughly what that costs
n101001,00010,000
O(1)1111
O(n)101001K10K

The parallel minimum stack meets the O(1)-per-operation target while using O(n) additional space.

Now try it on your own problem

Paste any LeetCode problem — or one of your own — and codebrew builds a walkthrough like this one for it.

Build a lesson from any problem

Related problems