Problem overview
A queue gives values back in the order they arrived, like a line at a shop. A stack does the opposite: the last value pushed leaves first. The task is to build a queue out of two stacks, using only what a stack can do: add a value on top, look at or remove the top value, and check whether it is empty.
The class MyQueue has four methods. push(int x) adds x at the back. peek() returns the front value without removing it. pop() removes the front value and returns it. empty() returns true if the queue holds no values. The constraints say pop and peek are only called on a non-empty queue. The tests print booleans as True and False, while Java's own spelling is true and false.
Here is a session. 1 arrived before 2, so it leaves first. 3 arrives after 2, so 2 is still the front when 3 is pushed.
MyQueue q = new MyQueue();
q.push(1);
q.push(2);
System.out.println(q.pop());
q.push(3);
System.out.println(q.peek() + " " + q.pop() + " " + q.pop() + " " + q.empty());
1
2 2 3 true
Approach: reverse the order once, by moving values
Popping a stack gives the newest value first, but a queue needs the oldest. Moving values from one stack to another reverses their order: the newest value leaves the first stack first and lands at the bottom of the second, and the oldest leaves last and lands on top. So the second stack has the oldest value on top, which is the front of the queue.
The simple version moves everything on every pop: move all values to the second stack, pop its top, then move everything back. Each pop then costs O(n) for n values in the queue.
The better idea is to not move them back. The lesson uses two stacks with fixed roles:
inStacktakes every pushed value. Its top is the newest value.outStackgives values back. Its top is the oldest value, the front of the queue.
A value is moved only when outStack is empty and the front is needed. While outStack still holds values, they all arrived before anything in inStack, so its top is still the front and nothing has to move. The queue's values are split between the two stacks, with the older ones in outStack.
Here is the state of the two stacks (Java prints a Stack from bottom to top) after pushing 1, 2 and 3, after a peek(), and after one more push of 4. The code reads the fields directly, which a user of the class would not do.
MyQueue q3 = new MyQueue();
q3.push(1); q3.push(2); q3.push(3);
System.out.println("in=" + q3.inStack + " out=" + q3.outStack);
System.out.println(q3.peek());
System.out.println("in=" + q3.inStack + " out=" + q3.outStack);
q3.push(4);
System.out.println("in=" + q3.inStack + " out=" + q3.outStack);
in=[1, 2, 3] out=[]
1
in=[] out=[3, 2, 1]
in=[4] out=[3, 2, 1]
The peek() moved all three values over and their order flipped, so 1 is on top of outStack. The 4 waits in inStack and will be moved only after 1, 2 and 3 have left. Three pops return 1, 2 and 3, and the next pop moves the 4 over and returns it.
Why it is amortized O(1)
One pop or peek may move many values, so a single call can be slow. But look at one value's life: it is pushed onto inStack once, moved to outStack at most once, and popped from outStack once. Three operations in total, whatever the sequence of calls. A value is never moved back, because the stacks have fixed roles.
So n operations cause at most about 3n stack operations in total, which is O(n), and O(1) per operation on average. This is called amortized O(1). To see it concretely, take a queue where 1,000 values are pushed, and after every third push a pop is done, and finally the rest is popped. That is 1,000 pushes and 1,000 pops, and the total number of moves between the stacks is exactly 1,000, one per value, even though the single most expensive call moved 637 values. The cost of that call is paid for by all the cheap calls around it.
Step by step
- Declare
inStack, aStack<Integer>starting empty. Every pushed value goes on top of it. - Declare
outStackthe same way. The values leave from it, oldest on top. push: pushxontoinStack.peek: write aniffor whenoutStackis empty. Values move only then.- Inside it, loop while
inStackis not empty. - In the loop, pop the top of
inStackand push it ontooutStack, in one line. The newest value leaves first and lands deepest, so the oldest ends up on top. - After the
if, returnoutStack.peek(). Whether values were just moved or not, the oldest value is on top, and reading it removes nothing. pop: call this class's ownpeek()and keep the result infront. That moves values when needed and finds the front.- Pop the top of
outStack, which removes the front. - Return
front, which was read before it was removed. empty: the values are split between the stacks, so the queue is empty only when both are:inStack.isEmpty() && outStack.isEmpty().
The solution in Java
class MyQueue {
Stack<Integer> inStack = new Stack<>();
Stack<Integer> outStack = new Stack<>();
public void push(int x) {
inStack.push(x);
}
public int peek() {
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.peek();
}
public int pop() {
int front = peek();
outStack.pop();
return front;
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
A few details about the Java code:
java.util.Stack. It haspush,pop,peekandisEmpty, all working at the top.popandpeekon an emptyStackthrowEmptyStackException, which the constraints rule out.- Printing a
Stack. It prints from the bottom to the top, so[3, 2, 1]has 1 on top. - Calling
peek()frompop(). Inside a class a method calls another method of the same object by its name alone.popreuses all ofpeek's work. - Boxing. The stacks hold
Integerobjects.push(int x)boxes on the way in, andint front = peek()unboxes on the way out, because the methods returnint. - Everyday Java. Outside this exercise, a queue is an
ArrayDequeor aLinkedList, both of which implementQueue. The exercise is about building one from stacks.
Seeing it work
A new queue is empty. After push(7) it is not, and after pop() returns 7 it is empty again.
MyQueue q2 = new MyQueue();
System.out.println(q2.empty());
q2.push(7);
System.out.println(q2.empty());
System.out.println(q2.pop());
System.out.println(q2.empty());
true
false
7
true
Note that empty() checks both stacks. After push(7) the value sits in inStack and outStack is empty. After a peek() the value is in outStack and inStack is empty. In both states the queue is not empty.
Complexity
Time: amortized O(1) per operation. push and empty are always O(1). peek and pop take O(1) when outStack has values, and O(m) when they must move m values. Each value is moved at most once in its lifetime, so n operations take O(n) in total. Moving all the values over and back on every pop would take O(n) per call.
Space: O(n) for the n values currently in the queue, spread over the two stacks.
Edge cases and common mistakes
- Moving values while
outStackstill has some. This breaks the order. IfoutStackholds 2 and a newer 3 sits ininStack, moving 3 on top of 2 would make 3 leave first. - Moving values back. Putting everything back into
inStackafter eachpopworks, but each call is thenO(n). - Checking only one stack in
empty(). The values can be in either stack. - Removing in
peek.peekmust read the top ofoutStackwithout popping it. Onlypopremoves. - Using
||instead of&&inempty(). That would call the queue empty when only one stack is. - Interleaved pushes and pops. A push after some pops goes on
inStackand waits untiloutStackruns out, as the 4 did above. This is what keeps the order right.
