LeetCode #155 Medium

Min Stack

A stack that also returns its minimum in O(1).

stackdesign
Open on LeetCode ↗
02

Intuition

The minimum only changes when you push something smaller or pop the current champion. So store, alongside each element, the minimum as of that element — pop automatically restores the older minimum. History travels with the stack.

How to spot this pattern

When a structure must report an aggregate in O(1), store the aggregate alongside each entry instead of recomputing it. Each frame remembers the minimum as of that push, so popping restores the previous minimum for free — no recalculation, no auxiliary scan. The same idea gives you a max-stack or a stack that tracks running sums.

03

Approach

1

Pair each entry with a running min

Push (x, min(x, current_min)). Top of stack always knows the whole stack's minimum.

2

Pop restores for free

Removing the top removes its snapshot too — the next pair's min was computed without it.

3

Two-stack variant

A second stack holding only new minima saves space when minima are rare; same idea.

04

Solution & live demo

1class MinStack:
2 def __init__(self):
3 self.stack = [] # (value, min_so_far)
4 
5 def push(self, val):
6 m = min(val, self.stack[-1][1]) if self.stack else val
7 self.stack.append((val, m))
8 
9 def pop(self):
10 self.stack.pop()
11 
12 def top(self):
13 return self.stack[-1][0]
14 
15 def getMin(self):
16 return self.stack[-1][1]
05

Common pitfalls

Keeping a single min variable

✗ Wrong
def push(self, val):
    self.stack.append(val)
    self.min = min(self.min, val)

def pop(self):
    self.stack.pop()
✓ Right
m = min(val, self.stack[-1][1]) if self.stack else val
self.stack.append((val, m))

Popping the current minimum leaves self.min pointing at a value that is no longer in the stack, and there is no way to recover the previous one without scanning. History has to be stored per frame, because popping must undo it.

Recomputing the minimum on demand

✗ Wrong
def getMin(self):
    return min(self.stack)
✓ Right
def getMin(self):
    return self.stack[-1][1]

Correct but O(n) per call, and the problem explicitly asks for constant time on every operation. The minimum was already known at push time — storing it turns the query into a lookup.

Seeding the minimum with zero

✗ Wrong
self.min = 0
✓ Right
m = val if not self.stack else min(val, self.stack[-1][1])

Zero is a real value that beats any positive input, so a stack of [3, 5] would report a minimum of 0 — a number never pushed. The first element's minimum is itself.

06

Edge cases

getMin on fresh stack

Guard empty; problem guarantees valid calls.

Duplicate minima

Each copy snapshots itself as min, so popping one still leaves the other's snapshot correct.

07

Complexity

Time
O(1) all ops
Space
O(n)
One extra number per element.