Stacks & Queues · 8 of 38
Design a Stack that supports getMin() in O(1) time and O(1) extra space
Keep a pile of numbers that can tell you its smallest number instantly, by hiding the old smallest number inside a special note on the pile.
- Simple way
- time O(1) per operation
room O(n) extra - Clever way
- time O(1) per operation
room O(1) extra
The problem, simply
Build a stack with push(x), pop(), top() and one more action, getMin(), which gives the smallest number in the stack right now.
All four must take O(1) time. The hard version also says: use only O(1) extra room. You may keep the stack itself, but not a second stack beside it.
A story to remember it
Picture it
We pushed 5, 3, 7, 2, 8. The pile we store holds two special notes (both 1), written when 3 and 2 became the new smallest. The clipboard says min = 2.
- min
- 2
- checking
- answer
Examples
The classic run
- In
push(-2), push(0), push(-3), getMin(), pop(), top(), getMin()- Out
-3, then top = 0, then -2
After -3 leaves, the smallest left is -2 again. The stack must remember it.
Several new lows
- In
push 5, 3, 7, 2, 8, then pop twice, getMin()- Out
3
Popping 8 and then 2 brings the smallest back to 3.
A repeated smallest
- In
push(3), push(1), push(1), pop(), getMin()- Out
1
The second 1 is equal to the min, not smaller, so it is stored plainly. After it leaves, the first 1 is still there.
Huge numbers
- In
push(2147483647), push(-2147483648), getMin(), pop(), getMin()- Out
-2147483648, then 2147483647
The special note is 2x - min, which does not fit in an int here. Store long values and it works.
Way 1: the simple way
Keep a second pile next to the first. Each time you push x, also push "the smallest so far" onto the second pile. Each time you pop, pop both piles.
push(x): pushxon the main pile. Pushmin(x, minPile.top)on the min pile.pop(): pop both piles.getMin(): read the top of the min pile.
Main pile and min pile after pushing 5, 3, 7, 2, 8. Each level of the min pile remembers the smallest value at or below that level.
- answer
class MinStack {
private final Deque<Integer> main = new ArrayDeque<>();
private final Deque<Integer> mins = new ArrayDeque<>();
public void push(int x) {
main.push(x);
mins.push(mins.isEmpty() ? x : Math.min(x, mins.peek()));
}
public void pop() {
main.pop();
mins.pop(); // both piles always have the same height
}
public int top() { return main.peek(); }
public int getMin() { return mins.peek(); }
}How long it takes: every action is O(1). Extra room: a second pile as tall as the first, O(n).
Way 2: the clever way
Keep only one pile and one number, min. When a new value is not a new low, push it as it is. When it is a new low, we must remember the old min somewhere, so we push a special note instead:
- note =
2 * x - min
Because x < min, the note is smaller than x. Every real value on the pile is at least the current min, so a stored value below min must be a note.
push(x): if the pile is empty, pushxand setmin = x. Else ifx < min, push2 * x - minand setmin = x. Else pushx.top(): if the stored top is belowmin, it is a note, and the real value ismin. Otherwise it is the real value.pop(): if the popped valueyis belowmin, it was a note, so the old minimum comes back:min = 2 * min - y.getMin(): returnmin.
Why does the math work? We stored y = 2x - old, and min became x. So 2 * min - y = 2x - (2x - old) = old.
Pushing 5, 3, 7, 2, 8 and popping them with one number on the side
01 / 10
push(5). The pile is empty, so store 5 and set min = 5.
- min
- 5
- looking now
- checking
- answer
- just added
class MinStackO1 {
private final Deque<Long> pile = new ArrayDeque<>();
private long min;
public void push(int x) {
if (pile.isEmpty()) {
pile.push((long) x);
min = x;
} else if (x < min) {
pile.push(2L * x - min); // a note that hides the old min
min = x;
} else {
pile.push((long) x);
}
}
public void pop() {
long y = pile.pop();
if (y < min) min = 2 * min - y; // it was a note: bring the old min back
}
public int top() {
long y = pile.peek();
return (int) (y < min ? min : y);
}
public int getMin() { return (int) min; }
}How long it takes: every action does a little math, O(1). Extra room: one number, min, so O(1) beyond the pile itself.
Watch out for
- Order matters in
push. Work out the note with the oldminfirst, then setmin = x. Doing it the other way round stores2x - x = x, and the old minimum is lost. top()must decode too. If the stored top is a note, the real value ismin.- Calling
pop,toporgetMinon an empty stack. Agree with the interviewer: throw, or return a sentinel.
What interviewers ask next
- Can you save space in the two pile version? Push to the min pile only when
x <= minPile.top, and pop it only when the popped value equals its top. Ties need the<=, or the min pile loses a copy too early. - Can you do
getMax()? Yes, flip it: whenx > max, store2 * x - max, and a stored value abovemaxis a note. - Store pairs instead. Push
(x, minSoFar)together. Clean and easy to explain, but it is the sameO(n)extra room as Way 1. - A queue with
getMin()inO(1). Build the queue from two min stacks. Each value is moved between them at most once, so it is amortizedO(1). - Why is the second way rarely used in production? It only works for numbers you can do math on, and it is easy to get overflow wrong. Interviewers ask it to see if you can reason about invariants.
Remember this
Grown-up words: the clipboard is the min variable, the special ticket is an encoded value, the second pile in Way 1 is an auxiliary min stack, and "everything real is at least min" is the invariant that makes notes easy to spot.