Skip to content

155. Min Stack (Medium)

Problem

Design a stack that supports push, pop, top, and getMin, all in O(1)O(1) time.

Example

MinStack ms = new MinStack();
ms.push(-2); ms.push(0); ms.push(-3);
ms.getMin(); // -3
ms.pop();
ms.top(); // 0
ms.getMin(); // -2

LeetCode 155 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, scan on getMin

Use a plain stack; compute min(stack) on every getMin call.

class MinStack:
def __init__(self):
self.stack = [] # L1: O(1)
def push(self, val: int) -> None:
self.stack.append(val) # L2: O(1) amortized
def pop(self) -> None:
self.stack.pop() # L3: O(1)
def top(self) -> int:
return self.stack[-1] # L4: O(1)
def getMin(self) -> int:
return min(self.stack) # L5: O(n) full scan

Where the time goes, line by line

Variables: n = number of elements currently on the stack.

LinePer-call costTimes executedContribution
L2 (push)O(1)O(1) amortizedper callO(1)O(1) per push
L3 (pop)O(1)O(1)per callO(1)O(1) per pop
L4 (top)O(1)O(1)per callO(1)O(1) per top
L5 (getMin scan)O(n)O(n)per callO(n)O(n) per getMin ← bottleneck

min(stack) must scan all n elements every time.

Complexity

  • push, pop, top: O(1)O(1).
  • getMin: O(n)O(n).
  • Space: O(n)O(n).

Fails the problem’s O(1)O(1) requirement for getMin.

Approach 2: Auxiliary min-stack

Maintain a parallel stack of running minimums. On push, push min(val, current_min) onto the aux stack; on pop, pop both.

class MinStack:
def __init__(self):
self.stack = [] # L1: O(1)
self.mins = [] # L2: O(1) parallel min-stack
def push(self, val: int) -> None:
self.stack.append(val) # L3: O(1)
self.mins.append(val if not self.mins else min(val, self.mins[-1])) # L4: O(1)
def pop(self) -> None:
self.stack.pop() # L5: O(1)
self.mins.pop() # L6: O(1)
def top(self) -> int:
return self.stack[-1] # L7: O(1)
def getMin(self) -> int:
return self.mins[-1] # L8: O(1) top of min-stack

Where the time goes, line by line

Variables: n = number of elements currently on the stack.

LinePer-call costTimes executedContribution
L3, L4 (push both stacks)O(1)O(1)per pushO(1)O(1) per push
L5, L6 (pop both stacks)O(1)O(1)per popO(1)O(1) per pop
L7 (top)O(1)O(1)per callO(1)O(1) per top
L8 (getMin)O(1)O(1)per callO(1)O(1) per getMin ← optimal

Every operation is O(1)O(1); the mins stack mirrors every push/pop.

Complexity

  • All operations: O(1)O(1).
  • Space: O(n)O(n) + O(n)O(n) = O(n)O(n).

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 3: Single stack of (value, running_min) tuples (optimal, one container)

Same asymptotics as Approach 2 but with one container instead of two.

class MinStack:
def __init__(self):
self.stack = [] # L1: O(1) list of (value, running_min)
def push(self, val: int) -> None:
cur_min = val if not self.stack else min(val, self.stack[-1][1]) # L2: O(1)
self.stack.append((val, cur_min)) # L3: O(1) amortized
def pop(self) -> None:
self.stack.pop() # L4: O(1)
def top(self) -> int:
return self.stack[-1][0] # L5: O(1) first element of top tuple
def getMin(self) -> int:
return self.stack[-1][1] # L6: O(1) second element of top tuple

Where the time goes, line by line

Variables: n = number of elements currently on the stack.

LinePer-call costTimes executedContribution
L2, L3 (push: compute min + append tuple)O(1)O(1)per pushO(1)O(1) per push
L4 (pop)O(1)O(1)per popO(1)O(1) per pop
L5 (top)O(1)O(1)per callO(1)O(1) per top
L6 (getMin)O(1)O(1)per callO(1)O(1) per getMin ← optimal

All operations remain O(1)O(1) by keeping the running minimum embedded in each stack frame.

Complexity

  • All operations: O(1)O(1).
  • Space: O(n)O(n).

Optional refinement: store only min-changes on a second stack

A third variant pushes to the aux min-stack only when a new minimum is established (and pops when popping a value equal to the current min). Saves memory on heavily-duplicated stacks. Same asymptotics.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

Approachpush/pop/topgetMinSpace
Scan on getMinO(1)O(1)O(n)O(n)O(n)O(n)
Auxiliary min-stackO(1)O(1)O(1)O(1)O(n)O(n)
Tuple stackO(1)O(1)O(1)O(1)O(n)O(n)

The tuple-stack and auxiliary-stack approaches are equivalent in Big-O. Pick by taste.

Test cases

# Quick smoke tests, paste into a REPL or save as test_min_stack.py and run.
# Uses the canonical implementation (Approach 3: tuple stack).
class MinStack:
def __init__(self):
self.stack = []
def push(self, val: int) -> None:
cur_min = val if not self.stack else min(val, self.stack[-1][1])
self.stack.append((val, cur_min))
def pop(self) -> None:
self.stack.pop()
def top(self) -> int:
return self.stack[-1][0]
def getMin(self) -> int:
return self.stack[-1][1]
def _run_tests():
ms = MinStack()
ms.push(-2)
ms.push(0)
ms.push(-3)
assert ms.getMin() == -3
ms.pop()
assert ms.top() == 0
assert ms.getMin() == -2
# Push in increasing order
ms2 = MinStack()
ms2.push(1)
ms2.push(2)
ms2.push(3)
assert ms2.getMin() == 1
ms2.pop()
assert ms2.getMin() == 1
# Single element
ms3 = MinStack()
ms3.push(5)
assert ms3.top() == 5
assert ms3.getMin() == 5
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Stacks, carrying running aggregate state per frame is a classic pattern
  • Stack Parsing, the last open, first closed model for nested syntax and reversible operations.
  • Simulation, the explicit state model for executing rules exactly while keeping cases organized.