Queues: A Tale of Two Stacks
Category: Stacks and Queues
Difficulty: Medium
Implement a queue with two stacks.
The elements of the queue are integers, and the queue has three operations:
enqueue
, dequeue
, and peek
. enqueue(x)
adds an element
x
to the back of the queue. dequeue
returns and removes the element
at the front of the queue, and peek
just returns it without removing it.
The queue is a FIFO data structure, so the elements should be popped in the same
order they were pushed in.
The problem challenges you to implement a queue using two stacks, which are LIFO data structures. Popping from a stack gives the most recently pushed element, not the element that was pushed the earliest.
It's important to observe that you can flip a stack, or change it so the bottom elements are now on top and vice versa, by emptying it into another stack (popping each element from one stack and pushing it onto the new one in order). This is useful because, for our queue, we might enqueue some elements and push them onto a stack, but then if we want to dequeue some elements, those will be on the bottom. We can reverse the stack by flipping it into a second stack, and now the oldest elements will be on top instead of the newest, and we can implement dequeue by popping from this stack. Then, if we wanted to enqueue more elements, we could flip the stack rightside-up again and push them on top of the newest elements.
There's a key improvement we can make to this strategy, though. Think of the
rightside-up stack as the enqueue stack and the upside-down stack as the dequeue
stack. If we enqueue five elements (call them 1, 2, 3, 4, and 5), then we know
that the next five calls to dequeue should remove 1, 2, 3, 4, and 5 in that
order. Suppose we have our enqueue stack [1, 2, 3, 4, 5]
with the
rightmost element on top, and we receive a dequeue call. We fill the dequeue
stack, [5, 4, 3, 2, 1]
, and pop the 1 because it is the oldest element.
Now, suppose we want to enqueue more elements. We don't need to move everything
back into the enqueue stack to put the new elements on top of 5. We don't need
to change anything about the dequeue stack because it is already in the state
[5, 4, 3, 2]
, and we know that no matter what, 2, 3, 4, and 5 are the next
four elements to be dequeued. We can continue filling the enqueue stack and
removing from the dequeue stack, until the dequeue stack eventually becomes
empty and a dequeue is performed. Then, we can flip the enqueue stack into the
dequeue stack and continue dequeueing. In all, every element starts in the
enqueue stack, then moves to the dequeue stack at some point, and then finally
gets dequeued and exits the data structure.
Java 8:
static class MyQueue<T> {
private Stack<T> enqStack;
private Stack<T> deqStack;
public MyQueue() {
enqStack = new Stack<>();
deqStack = new Stack<>();
}
public void enqueue(T item) {
enqStack.push(item);
}
public T dequeue() {
peek();
return deqStack.pop();
}
public T peek() {
if (deqStack.isEmpty()) {
while (!enqStack.isEmpty()) {
deqStack.push(enqStack.pop());
}
}
return deqStack.peek();
}
}
C++:
class MyQueue {
public:
stack<int> stack_newest_on_top, stack_oldest_on_top;
void push(int x) {
stack_newest_on_top.push(x);
}
void pop() {
front();
stack_oldest_on_top.pop();
}
int front() {
if (stack_oldest_on_top.empty()) {
while (!stack_newest_on_top.empty()) {
stack_oldest_on_top.push(stack_newest_on_top.top());
stack_newest_on_top.pop();
}
}
return stack_oldest_on_top.top();
}
};
Python 3:
class MyQueue(object):
def __init__(self):
self.enq, self.deq = [], []
def peek(self):
if not self.deq:
while self.enq:
self.deq.append(self.enq.pop())
return self.deq[-1]
def pop(self):
self.peek()
self.deq.pop()
def put(self, value):
self.enq.append(value)