Design a stack that supports push, pop, top, and getMin — all in O(1). The trick: maintain a parallel min-stack so the current minimum is always one peek away.
Implement a stack class with four O(1) operations: push(val), pop(), top(), and getMin(). The first three are standard; the challenge is getMin()— it must return the minimum element currently in the stack in constant time, without scanning the whole stack.
Worked example:
push(5) → stack: [5]push(3) → stack: [5, 3]push(7) → stack: [5, 3, 7]getMin() → 3top() → 7pop() → stack: [5, 3]getMin() → 3 (still correct after pop)minStack) that mirrors the main stack — each slot stores min(val, minStack.top). When you pop the main stack, pop minStack too. The minimum is always at minStack.top, so getMin() is a single peek. No scanning, no heap, no sorting — just two stacks moving in lockstep.stack (normal values) and minStack (running minimums). They always have the same length.val to stack. Append min(val, minStack.top) to minStack (or just valif it's the first element). This records the minimum that would be correct if we stopped here.minStack.topautomatically reflects the new state.stack.top — standard peek.minStack.top — O(1) peek of the pre-computed running minimum.minStack when the new value is less than or equal to the current min (to save space). This works but breaks if the same minimum value appears twice — when you pop one copy, the second copy in minStack is also gone and future getMin() calls return the wrong answer. The simplest fix is to always push to both stacks, sacrificing a little space for correctness without special-casing.The parallel stack doubles memory from O(n) to O(2n) — still O(n). The "push only on new min" optimization reduces minStack size in the best case but requires careful handling of duplicate minimums. For interviews, stick to the always-push version unless asked to optimize space.
push records value + running minimum.1▶class MinStack {2▶ private stack: number[] = [];3▶ private minStack: number[] = [];45 push(val: number): void {6 this.stack.push(val);7 const curMin = this.minStack.length === 08 ? val9 : Math.min(val, this.minStack[this.minStack.length - 1]);10 this.minStack.push(curMin);11 }1213 pop(): void {14 this.stack.pop();15 this.minStack.pop();16 }1718 top(): number {19 return this.stack[this.stack.length - 1];20 }2122 getMin(): number {23 return this.minStack[this.minStack.length - 1];24 }25}
class MinStack {
private stack: number[] = [];
private minStack: number[] = [];
push(val: number): void {
this.stack.push(val);
const curMin = this.minStack.length === 0
? val
: Math.min(val, this.minStack[this.minStack.length - 1]);
this.minStack.push(curMin);
}
pop(): void {
this.stack.pop();
this.minStack.pop();
}
top(): number {
return this.stack[this.stack.length - 1];
}
getMin(): number {
return this.minStack[this.minStack.length - 1];
}
}stack holds the real values. minStack holds one number per level: the minimum of everything in stack up to and including that slot. Both arrays grow and shrink together.val to stack. For minStack, compute min(val, minStack.top)— "what's the smallest thing in the stack right now?" — and append that. This is the pre-computation that makes getMin O(1) later.minStack.topautomatically becomes the correct minimum for the remaining elements. No recomputation needed.stack. Standard O(1) array access.minStack. It already holds the running minimum by construction — O(1) and correct at every stack level.minStack when the new value is <= current min. But then on pop you must only pop minStack when the popped value equals the current min. This is trickier — know both variants.Math.min with Math.max — the structure is identical. LeetCode 716 asks exactly this.[val, curMin] tuples in a single stack. Same O(n) space and O(1) ops, just different packaging.pop, top, or getMin on an empty stack is undefined by the problem constraints — but know to guard against it in production.class MinStack {
private stack: number[] = [];
private minStack: number[] = [];
push(val: number): void {
this.stack.push(val);
const curMin = this.minStack.length === 0
? val
: Math.min(val, this.minStack[this.minStack.length - 1]);
this.minStack.push(curMin);
}
pop(): void {
this.stack.pop();
this.minStack.pop();
}
top(): number {
return this.stack[this.stack.length - 1];
}
getMin(): number {
return this.minStack[this.minStack.length - 1];
}
}Drop the parallel min-stack entirely: keep just one stack and, whenever getMin is called, walk the whole stack to find the smallest value. A clean correctness baseline before you cache the running minimum.
class MinStack {
private stack: number[] = [];
push(val: number): void {
this.stack.push(val);
}
pop(): void {
this.stack.pop();
}
top(): number {
return this.stack[this.stack.length - 1];
}
getMin(): number {
// Linear scan every time — no auxiliary structure.
let min = Infinity;
for (const v of this.stack) {
if (v < min) min = v;
}
return min;
}
}getMinis O(n). A workload that queries the minimum after every operation degrades to O(n²) overall. The parallel min-stack trades a little extra memory to make getMin O(1).| "getMin in O(1)" alongside push/pop | parallel minStack, always push both |
| stack + query on a derived property (min/max) | auxiliary stack tracking that property |
| O(1) range-min/max on a growing prefix | running min/max stored per level |
| "design a data structure" with constant-time queries | augment the structure with a companion stack/array |
class MinStack {
private stack: number[] = [];
private minStack: number[] = [];
push(val: number): void {
this.stack.push(val);
const curMin = this.minStack.length === 0
? val : Math.min(val, this.minStack[this.minStack.length - 1]);
this.minStack.push(curMin);
}
pop(): void { this.stack.pop(); this.minStack.pop(); }
top(): number { return this.stack[this.stack.length - 1]; }
getMin(): number { return this.minStack[this.minStack.length - 1]; }
}minStack stores min(val, prevMin) at each level. getMin() is just minStack.top.push, pop, top, getMin) in the auxiliary min-stack solution?