Min Stack
Parallel minimum stack
Auxiliary structure trick: maintain a parallel stack of minimums.
Problem
Design a stack that supports push, pop, top, and getMin in constant time.
getMin must return the minimum element currently in the stack.
Intuition
The trick is not to search for the minimum after the fact. That would make getMin linear.
Instead, cache the minimum at each depth of the stack. Every time you push a value, also push the minimum seen so far. Then the current minimum is always the top of the auxiliary stack.
This is the same design move you use in production systems when a derived value is queried often: pay the cost once on mutation, then read it in O(1).
Approach
Parallel Minimum Stack
O(1)O(n)Maintain two stacks:
valuesstores the actual pushed values.minsstores the minimum value for the prefix ending at the same depth.
On push(x), append x to values and min(x, current_min) to mins.
On pop(), remove one item from both stacks.
top() and getMin() are just reads from the back.
pub struct MinStack {
values: Vec<i32>,
mins: Vec<i32>,
}
impl MinStack {
pub fn new() -> Self {
Self {
values: Vec::new(),
mins: Vec::new(),
}
}
pub fn push(&mut self, x: i32) {
let min_so_far = match self.mins.last().copied() {
Some(curr) => curr.min(x),
None => x,
};
self.values.push(x);
self.mins.push(min_so_far);
}
pub fn pop(&mut self) {
self.values.pop();
self.mins.pop();
}
pub fn top(&self) -> i32 {
*self.values.last().unwrap()
}
pub fn get_min(&self) -> i32 {
*self.mins.last().unwrap()
}
}
class MinStack {
private values: number[] = [];
private mins: number[] = [];
push(x: number): void {
const minSoFar =
this.mins.length > 0 ? Math.min(this.mins[this.mins.length - 1], x) : x;
this.values.push(x);
this.mins.push(minSoFar);
}
pop(): void {
this.values.pop();
this.mins.pop();
}
top(): number {
return this.values[this.values.length - 1];
}
getMin(): number {
return this.mins[this.mins.length - 1];
}
}Language notes:
- Rust — mirror the two stacks exactly; it keeps the invariant obvious and avoids any branchy "recompute minimum" logic.
- TypeScript — the array-back pattern is the same as
Vec::push/Vec::pop; the implementation stays tiny becauseminsalready stores the prefix minimum.
Trace
Push -2, 0, -3, then ask for the minimum:
| op | values stack | mins stack | result |
|---|---|---|---|
| push -2 | [-2] | [-2] | - |
| push 0 | [-2, 0] | [-2, -2] | - |
| push -3 | [-2, 0, -3] | [-2, -2, -3] | - |
| getMin | [-2, 0, -3] | [-2, -2, -3] | -3 |
| pop | [-2, 0] | [-2, -2] | - |
| top | [-2, 0] | [-2, -2] | 0 |
| getMin | [-2, 0] | [-2, -2] | -2 |
The key invariant is visible in the second column: the top of mins always matches the minimum of values up to that depth.
Edge cases
- Duplicate minima — push the same minimum again when the new value ties the current minimum.
- Negative values — no special handling; the minimum logic is purely comparative.
- Popping the last element — both stacks shrink together, so the data structure stays aligned.
This is the simplest version of a broader pattern: store a derived aggregate per prefix. The same idea appears in prefix sums, rolling minima, and persistent snapshots.
Takeaway
If a query depends on all items currently in a stack, ask whether that answer can be cached on the way in. That is the whole trick here.
The implementation is tiny, but the design pattern is important: build the expensive part into the mutation path so the read path stays O(1).
More in Stack & Queue Fundamentals
The stack’s ‘hello world’. Match most-recent open bracket.
Stack as an accumulator. This pattern scales to expression parsing.
Your first monotonic stack! ‘Next greater element’ pattern.
Stack with a physical intuition — cars merge when a faster one catches a slower one.