Problem overview
A stack gives values back in the opposite order they arrived: the last value pushed is the first one popped. The task is to build a MinStack class with four operations: push(val), pop(), top() and getMin(). The last one must return the smallest value currently in the stack, and every operation has to run in constant time, O(1).
Take the first example: push -2, push 0, push -3. The stack now holds -2, 0 and -3, with -3 on top, so getMin() gives -3. After pop() removes -3, the value on top is 0 and the smallest value is -2 again. The outputs are -3 0 -2.
The second example shows duplicates: after pushing 5, 5 and 7 and popping twice, one 5 is still in the stack, so the minimum is still 5.
Approach: remember the minimum under every value
The obvious way to answer getMin() is to look through the whole stack, which costs O(n) per call. Keeping a single variable min is quicker but breaks as soon as the minimum is popped: the stack then has to find the second smallest value, and a single number can't tell it.
The way out is an observation about stacks. A value leaves the stack only after every value above it has left. While it is in the stack, the values below it can't change. So the minimum of "this value and everything below it" is fixed at the moment the value is pushed, and it can be written down once.
The class therefore keeps two stacks of the same height. stack holds the values. minStack holds, at each depth, the smallest value from the bottom of the stack up to that depth. The top of minStack always describes the whole stack as it is now, so getMin() only has to read it. Popping from both stacks together uncovers the minimum the stack had before the popped value arrived.
Here are the two stacks after each push in the first example, copied from a run of the solution below:
| Operation | stack | minStack |
|---|---|---|
push(-2) | [-2] | [-2] |
push(0) | [-2, 0] | [-2, -2] |
push(-3) | [-2, 0, -3] | [-2, -2, -3] |
pop() | [-2, 0] | [-2, -2] |
The bottom of each list is on the left and the top is on the right. After the pop, the top of minStack is -2 again, with no searching at all.
Step by step
- Declare the value stack as a field of type
Stack<Integer>, created withnew Stack<>(). - Declare
minStackthe same way. Its entry at each depth is the smallest of the value at that depth and everything below it. - In
push, pushvalontostackfirst, like on any stack. - Start
currentMinatval. If the stack was empty,valis its only value and so its minimum. - If
minStackisn't empty, lowercurrentMinwithMath.minagainst the top ofminStack. That top is already the smallest of everything underval, so one comparison is enough. - Push
currentMinontominStack. Both stacks now have the same size. Equal values are pushed too, so a minimum that was pushed twice is remembered twice. - In
pop, pop from both stacks. They must stay the same size. - In
top, returnstack.peek(), which reads the top without removing it. - In
getMin, returnminStack.peek().
The solution in Java
class MinStack {
Stack<Integer> stack = new Stack<>();
Stack<Integer> minStack = new Stack<>();
public void push(int val) {
stack.push(val);
int currentMin = val;
if (!minStack.isEmpty()) {
currentMin = Math.min(currentMin, minStack.peek());
}
minStack.push(currentMin);
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
The Java details worth a closer look:
Stack<Integer>and unboxing. AStackholds objects, so theintvalues are boxed intoInteger.Math.min(currentMin, minStack.peek())andreturn stack.peek()unbox them back tointautomatically. If you compare two boxed values yourself, useequalsor unbox first: with twoIntegerobjects holding 1000,==givesfalseandequalsgivestrue, because==compares references.push,popandpeekall work at the top.java.util.Stackadds to and removes from the end of its internal list.peek()reads the top; onlypop()removes it.- Empty stacks throw.
pop()andpeek()on an emptyStackthrowjava.util.EmptyStackException. The lesson only callspop,topandgetMinon a non-empty stack, so the code needs no guard. Stackis an older class. It is built onVector, and Java's own documentation recommendsDequefor new code.ArrayDequeofferspush,popandpeektoo, but at the front:push(4)thenpush(9)prints as[9, 4], with 9 on top. Either works here. The lesson usesStackbecause its method names match the problem.- No constructor needed. The fields are initialised where they are declared, so the default constructor already gives the empty
MinStackthe statement asks for.
Complexity
Time: O(1) per operation. push does two pushes, one comparison and one peek. pop does two pops. top and getMin each do one peek. Pushing to the end of a Stack is amortised constant, because the underlying array occasionally doubles.
Space: O(n). For n values in the stack, minStack holds exactly n extra entries, one per value.
Edge cases and common mistakes
- Keeping one
minvariable. It is wrong after the minimum is popped, because nothing remembers the previous minimum. - Skipping duplicates in
minStack. Pushing tominStackonly when the value is a new minimum is a common variation, but thenpopmust compare before popping. The lesson avoids that by pushing an entry every time, which keeps the two stacks aligned. With 5, 5, 7 and two pops, the code returns 5 for the minimum. - Popping only one stack. The stacks fall out of step, and
getMinthen describes the wrong depth. - Comparing with
top(). The top value and the minimum are different things. After pushing 3, 1 and 2,top()is 2 andgetMin()is 1. - Extreme values. Pushing
Integer.MIN_VALUEand thenInteger.MAX_VALUEgives atop()of 2147483647 and agetMin()of -2147483648.Math.mincompares and never subtracts, so nothing overflows.
Testing
- Push -2, 0, -3, then
getMin(),pop(),top(),getMin()gives-3 0 -2. - Push 5, 5, 7, then
getMin(),pop(),pop(),getMin(),top()gives5 5 5. - Push 3, then
top(),getMin(), push 1, push 2, thentop(),getMin(),pop(),pop(),getMin()gives3 3 2 1 3. - Push
Integer.MIN_VALUEandInteger.MAX_VALUE, thentop()andgetMin()give2147483647 -2147483648.
