Queue using Stacks
Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue: push, peek, pop, and empty. void push(int x) pushes element x to the back. int pop() removes and returns the front element. int peek() returns the front element. boolean empty() returns true if empty. Each operation must only use standard stack operations.
- 1 <= x <= 9
- At most 100 calls to push, pop, peek, and empty
- All calls to pop and peek are valid (queue will not be empty)
push(1), push(2), peek(), pop(), empty()1, 1, falsepush(1), pop(), push(2), peek()1, 2Two structures collide here, so start by pinning down what each one is. A stack is a pile you touch only from the top: push sets a value on top, pop removes the top one — the last thing in is the first out (LIFO). A queue is a line at a checkout: you join at the back and leave from the front — the first thing in is the first out (FIFO). The problem hands us only stacks and asks us to behave like a queue. The two orderings are exact opposites, which is precisely the crack we can exploit.
Pour one stack into another and the order flips. Push 1, 2, 3 onto stack A (top is 3). Now repeatedly pop A and push each into stack B: B receives 3, then 2, then 1, so B's top is 1. The element that went into A first (the 1, buried at A's bottom) is now sitting on top of B, first to come out. That is exactly the queue's front. So a single reversal turns LIFO into FIFO. The whole design is: arrivals pile up in one stack, and when we need to serve someone, a reversal exposes the oldest.
Keep an inStack for arrivals and an outStack for departures. New items always push onto inStack. To pop or peek the queue's front, we need the oldest item — which lives at the bottom of inStack, unreachable directly. So we pour inStack into outStack (the reversal), and now the oldest sits on top of outStack, ready to serve.
The one subtlety that makes this efficient: only pour when outStack is empty. As long as outStack still has items, its top is already the correct next-to-serve — don't touch inStack. New arrivals keep stacking on inStack meanwhile. We refill outStack only once it runs dry, in one batch.
class MyQueue:
def __init__(self):
self.in_stack = []
self.out_stack = []
def push(self, x):
self.in_stack.append(x) # arrivals always go here
def _transfer(self):
if not self.out_stack: # ONLY refill when empty
while self.in_stack:
self.out_stack.append(self.in_stack.pop())
def pop(self):
self._transfer()
return self.out_stack.pop() # oldest is on top after reversal
def peek(self):
self._transfer()
return self.out_stack[-1]
def empty(self):
return not self.in_stack and not self.out_stackif not self.out_stack guard is the entire trick — do not pour on every pop. If you transferred every time, items already reversed into outStack would get poured back into inStack and re-reversed, scrambling the order and wasting work. Pour only into an empty outStack, drain it completely before refilling, and the order is always correct.A single pop that triggers a transfer of N items looks like O(N) — and occasionally it is. But track any one element's whole life: it is pushed onto inStack once, popped from inStack once, pushed onto outStack once, popped from outStack once. Four touches, forever — no element is ever moved twice by the same transfer, because we drain outStack fully before refilling. Spread the cost of the occasional expensive transfer across all the cheap operations that element also took part in, and the average cost per operation is constant. That averaged-out cost is what "amortized O(1)" means: not every call is O(1), but any long run of calls totals O(1) each.
push(1), push(2) → inStack [1, 2] (top 2), outStack [].peek() → outStack empty, so transfer: pop 2→push, pop 1→push. Now outStack [2, 1] (top 1). Return 1 (the oldest, correctly).push(3) → goes onto inStack: inStack [3], outStack [2, 1]. Note we did not disturb outStack.pop() → outStack non-empty, so no transfer; pop its top → 1. outStack [2].pop() → still non-empty; pop → 2. outStack [].pop() → outStack now empty, transfer the waiting 3: outStack [3]; pop → 3. Served order 1, 2, 3 — true FIFO.Train the reflex: when you're handed one structure and asked to mimic its opposite, look for a reversal the target ordering wants, and delay that reversal until it's actually needed. The mirror version of this exact idea — simulating a stack out of queues — is the very next problem.
Bridging LIFO and FIFO with Dual Stacks
The Reversal Insight
A single stack reverses order (LIFO). By transferring to a second stack, we reverse the reversal, effectively yielding the original order (FIFO).
Strategy: Lazy Transfer
Don't move elements unnecessarily. Only transfer from Inbox (S1) to Outbox (S2) when S2 is empty and a Pop/Peek is requested.