Stacks & Queues · 23 of 38
Implement Queue using Stack
Make a fair first come, first served line using only two piles, by pouring one pile into the other only when you need to.
- Simple way
- time O(n) per push
room O(n) - Clever way
- time O(1) amortized per operation
room O(n)
The problem, simply
Build a queue, a first in first out line, but you may only use stacks inside it. A stack can only push, pop, peek at the top, and say if it is empty.
You need push(x) to add at the back, pop() to remove from the front, peek() to see the front, and empty().
A story to remember it
Picture it
Two stacks after push 1, 2, 3, then a pop (which poured them over and took 1), then push 4. The out stack has the oldest values with 2 on top. The line they act like is 2, 3, 4.
- looking now
Examples
A normal run
- In
push(1), push(2), peek(), pop(), empty()- Out
peek = 1, pop = 1, empty = false
1 came first, so it is the front. After it leaves, 2 is still waiting.
Pushing after a pour
- In
push(1), push(2), push(3), pop(), push(4), pop(), pop(), pop()- Out
pops give 1, 2, 3, 4
4 joins the in stack while 2 and 3 are still in the out stack. The out stack must be emptied before 4 is poured over, or 4 would jump the line.
Peek does the pour too
- In
push(5), peek(), peek()- Out
5, 5
The first peek finds the out stack empty and pours. The second peek finds 5 already on top of out.
Empty check
- In
push(7), pop(), empty()- Out
pop = 7, empty = true
The queue is empty only when both stacks are empty.
Way 1: the simple way
Keep the whole line in one stack with the front on top. To push a new value at the back, it has to go to the bottom. So pour everything into the second stack, push the new value, and pour everything back.
push(x): move all ofs1intos2, pushxontos1, move all ofs2back ontos1.pop()andpeek(): use the top ofs1.
push(4) in Way 1. All three old values move to s2 so 4 can land at the bottom of s1. Then they all move back. Every push moves everything twice.
- checking
- just added
class MyQueue {
private final Deque<Integer> s1 = new ArrayDeque<>(); // front on top
private final Deque<Integer> s2 = new ArrayDeque<>();
public void push(int x) {
while (!s1.isEmpty()) s2.push(s1.pop()); // pour out
s1.push(x); // x goes to the bottom
while (!s2.isEmpty()) s1.push(s2.pop()); // pour back
}
public int pop() { return s1.pop(); }
public int peek() { return s1.peek(); }
public boolean empty() { return s1.isEmpty(); }
}How long it takes: each push moves all n values twice, O(n). Pop and peek are O(1). Extra room: the two stacks hold n values together, O(n).
Way 2: the clever way
Use the two cups. The in stack takes new values. The out stack hands out old values.
push(x): push ontoin. Nothing else.pop()orpeek(): ifoutis empty, pour all ofinontoout. The order flips, so the oldest value ends up on top. Then pop or peekout.empty(): both stacks are empty.
Only pour when out is empty. Values already in out are older than anything in in, so they must leave first.
An in stack, an out stack, and the line they make
01 / 10
Both stacks are empty, so the line is empty.
- looking now
- just added
class MyQueue {
private final Deque<Integer> in = new ArrayDeque<>(); // new values
private final Deque<Integer> out = new ArrayDeque<>(); // oldest on top
public void push(int x) {
in.push(x);
}
public int pop() {
pourIfNeeded();
return out.pop();
}
public int peek() {
pourIfNeeded();
return out.peek();
}
public boolean empty() {
return in.isEmpty() && out.isEmpty();
}
private void pourIfNeeded() {
if (out.isEmpty()) { // only when out has run dry
while (!in.isEmpty()) out.push(in.pop());
}
}
}How long it takes: one pop can pour n values, which is O(n) for that call. But each value is poured at most once in its life, so over any run of m actions the total work is O(m). That is O(1) amortized per action. Extra room: O(n) for the values.
Watch out for
empty()must check both stacks. Values may be waiting ininwhileoutis empty.peek()needs the same pour aspop(). Peeking atingives the newest value, not the oldest.- Amortized is not the same as worst case. A single
popcan still takeO(n). If the interviewer needs a strictO(1)per call, say so and discuss it.
What interviewers ask next
- Why is it amortized
O(1)? Each value goes through four steps: push toin, pop fromin, push toout, pop fromout. Four steps per value meansO(m)total formpushes. - Build a stack from queues instead. Use one queue: after each push, rotate the older values behind the new one, so the newest is at the front. Push becomes
O(n). - Do it with one stack and recursion. Push stays normal; to pop the bottom, pop down recursively and push back on the way out. The call stack is the second stack.
- Make a queue that can return its minimum in
O(1). Use two min stacks in this same design. - Where is this used? Functional languages build queues out of two immutable lists in exactly this way.
Remember this
Grown-up words: the cups are stacks, the line is a queue (first in first out), pouring is transferring, and "fast on average even if one call is slow" is amortized O(1).