LeetCode ยท #232

Implement Queue using Stacks Solution

Use two stacks and lazy transfer to simulate FIFO behavior with amortized O(1) queue operations.

01
Section One ยท Problem

Problem Statement

๐Ÿท๏ธ

Difficulty

Easy

๐Ÿ”—

LeetCode

Problem #232

๐Ÿ—๏ธ

Pattern

Design โ€” Two Stacks

Implement a FIFO queue using only standard stack operations: push, pop, top/peek, size, and empty.

Example
MyQueue q = new MyQueue(); q.push(1); q.push(2); q.peek(); // 1 q.pop(); // 1 q.empty(); // false
02
Section Two ยท Brute Force

Expensive Push

Keep all queue elements in one stack with the front always on top. On every push, move everything to a helper stack, add the new element, then move everything back.

Problem: This makes every push O(n). We can do better by delaying reversals until they are actually needed.
03
Section Three ยท Optimal

Two Stacks + Lazy Transfer

Use one stack for incoming elements and one for outgoing elements:

  • inStack: new pushes go here
  • outStack: pops and peeks come from here

When outStack is empty and you need the front, pour all elements from inStack into outStack. That reversal restores FIFO order.

๐Ÿ’ก Mental model: Think of inStack as arrivals and outStack as the departure gate. You only move everyone from arrivals to departures when the departure gate runs empty.
Why amortized O(1)?:
  • Every element moves from inStack to outStack at most once.
  • So even though one transfer step can cost O(n), the total cost spread across operations is linear.
04
Section Four ยท Walkthrough

Visual Walkthrough

Lazy transfer only when needed
push(1), push(2), pop(), push(3), peek() After push(1), push(2): in=[1,2], out=[] Need pop() and out is empty โ†’ transfer โ†’ out=[2,1] pop() returns 1, now out=[2] push(3) โ†’ in=[3], out=[2] peek() returns 2 without transfer because out is not empty FIFO order preserved โœ“
05
Section Five ยท Code

Code โ€” Java & Python

Java โ€” two stacks (using ArrayDeque)
import java.util.ArrayDeque; import java.util.Deque; class MyQueue { private final Deque<Integer> in = new ArrayDeque<>(); private final Deque<Integer> out = new ArrayDeque<>(); public void push(int x) { in.push(x); } private void moveIfNeeded() { if (!out.isEmpty()) return; while (!in.isEmpty()) out.push(in.pop()); } public int pop() { moveIfNeeded(); return out.pop(); } public int peek() { moveIfNeeded(); return out.peek(); } public boolean empty() { return in.isEmpty() && out.isEmpty(); } }
Python โ€” two stacks
class MyQueue: def __init__(self): self.ins = [] self.outs = [] def push(self, x: int) -> None: self.ins.append(x) def _move_if_needed(self): if self.outs: return while self.ins: self.outs.append(self.ins.pop()) def pop(self) -> int: self._move_if_needed() return self.outs.pop() def peek(self) -> int: self._move_if_needed() return self.outs[-1] def empty(self) -> bool: return not self.ins and not self.outs
06
Section Six ยท Analysis

Complexity Analysis

ApproachPushPop / PeekSpace
Expensive pushO(n)O(1)O(n)
Two stacks + lazy transferO(1)O(1) amortizedO(n)
07
Section Seven ยท Edge Cases

Edge Cases & Pitfalls

CaseWhat happensWhy it matters
Many pushes before first popOne bulk transfer happens laterThis is where amortized analysis matters.
Alternating push/popStill correctout only refills when empty.
Single element queuepeek() and pop() both return that elementThe transfer logic must handle tiny cases too.
โš  Common Mistake: Transferring from in to out on every pop or peek. Only transfer when out is empty; otherwise you lose the amortized O(1) benefit.

โ† Back to Deque practice