Implement a Queue Using Two Stacks

Solve this Problem
Easy20–25 min
Topics
Companies
Practice:LeetCode ↗GFG ↗
Implement a FIFO queueFIFOFirst In, First Out — the earliest-enqueued element is always the first one dequeued, the opposite order from a stack. using only stack operationsStack OperationsPush (add to the top), pop (remove from the top), and peek the top — a stack is LIFO (Last In, First Out). — push onto the top, pop from the top, peek the top — supporting enqueue(v), dequeue(), front(), isEmpty(), and size(). This version takes a fixed sequence of operations and replays them, returning the result of every dequeue, front, isEmpty, and size call as an array. This is the mirror image of building a stack from queues: a stack always hands back its newest element, the opposite of what a queue needs. One fix reorders on every enqueue, keeping the oldest value permanently on top so dequeue and front stay cheap. A better fix defers that reordering: let values pile up cheaply, and only pay the reordering cost when something actually needs to come off the front — and crucially, that cost only recurs once the "read-out" side runs dry, not on every single call. Spread across a whole sequence of operations, each element is only ever moved between the two stacks a constant number of times, giving every operation O(1) time on average even though individual dequeues can occasionally do more work than others.

Test Case 1:

Input:ops = [enqueue, enqueue, enqueue, dequeue, dequeue, front], args = [5, 10, 15, -, -, -]
Output:[5, 10, 15]
Explanation:enqueue 5, 10, 15 (in that order); dequeue returns 5 (first in), dequeue returns 10, front peeks at 15 without removing it — FIFO order.

Test Case 2:

Input:ops = [isEmpty, enqueue, isEmpty, dequeue, isEmpty], args = [-, 7, -, -, -]
Output:[1, 0, 7, 1]
Explanation:Starts empty (isEmpty→1). After enqueue(7) it's non-empty (isEmpty→0). dequeue removes and returns 7. Now it's empty again (isEmpty→1).

Test Case 3:

Input:ops = [enqueue, enqueue, size, dequeue, size], args = [1, 2, -, -, -]
Output:[2, 1, 1]
Explanation:After two enqueues, size→2. dequeue removes the oldest value (1) and returns it. size→1 afterward.

Constraints

  • 1 ≤ number of operations ≤ 12
  • Each operation is one of enqueue, dequeue, front, isEmpty, or size
  • 0 ≤ enqueued value ≤ 1000
  • dequeue and front return -1 when the queue is empty
  • Only stack operations (push onto the top, pop from the top, peek the top) may be used to store elements
  • This version replays a fixed sequence of operations and reports the result of every dequeue, front, isEmpty, and size call, in order (enqueue has no return value)
🚀

Try the Dry Run

Don't just read the solution — watch it execute, one step at a time.

Approach & Solutions

Two Stacks, Enqueue Costly

Brute

Keep the queue's contents in stack s1, oldest value always on top, so dequeue and front are trivial O(1) pops/peeks. To enqueue a new value while preserving that ordering: pour every element out of s1 into a helper stack s2 (which naturally reverses them), push the new value onto s2 — putting it underneath everything, since it's the newest and belongs at the bottom — then pour s2 back into s1, restoring the oldest-on-top order with the new value correctly at the very bottom. Every enqueue touches every existing element twice, so it costs O(n).

TimeO(n) per enqueue, O(1) per dequeue/front
SpaceO(n)
1class Solution { 2 public int[] queueOpsFromStacks(String[] ops, int[] args) { 3 Deque<Integer> s1 = new ArrayDeque<>(); 4 Deque<Integer> s2 = new ArrayDeque<>(); 5 List<Integer> results = new ArrayList<>(); 6 for (int i = 0; i < ops.length; i++) { 7 String op = ops[i]; 8 if (op.equals("enqueue")) { 9 while (!s1.isEmpty()) s2.push(s1.pop()); 10 s2.push(args[i]); 11 while (!s2.isEmpty()) s1.push(s2.pop()); 12 } else if (op.equals("dequeue")) { 13 results.add(s1.isEmpty() ? -1 : s1.pop()); 14 } else if (op.equals("front")) { 15 results.add(s1.isEmpty() ? -1 : s1.peek()); 16 } else if (op.equals("isEmpty")) { 17 results.add(s1.isEmpty() ? 1 : 0); 18 } else { 19 results.add(s1.size()); 20 } 21 } 22 int[] output = new int[results.size()]; 23 for (int i = 0; i < results.size(); i++) output[i] = results.get(i); 24 return output; 25 } 26}

Optimal — Two Stacks, Amortized O(1)

Optimal

Instead of reordering on every enqueue, defer the reordering until it's actually needed. enqueue always just pushes onto s1 — O(1), no matter what. dequeue and front first check a second stack, s2: if it's empty, pour all of s1 into it in one pass (which reverses the order, putting the oldest value on top of s2); then read from s2's top. Crucially, that pour only happens when s2 runs dry — every element gets poured across the two stacks at most once before it's eventually popped, so across any sequence of n operations the total pouring work is O(n), making every operation O(1) on average (amortized), even though a single dequeue can occasionally trigger an O(n) pour.

TimeO(1) per enqueue, O(1) amortized per dequeue/front
SpaceO(n)
1class Solution { 2 public int[] queueOpsFromStacks(String[] ops, int[] args) { 3 Deque<Integer> s1 = new ArrayDeque<>(); 4 Deque<Integer> s2 = new ArrayDeque<>(); 5 List<Integer> results = new ArrayList<>(); 6 for (int i = 0; i < ops.length; i++) { 7 String op = ops[i]; 8 if (op.equals("enqueue")) { 9 s1.push(args[i]); 10 } else if (op.equals("dequeue")) { 11 if (s2.isEmpty()) { 12 while (!s1.isEmpty()) s2.push(s1.pop()); 13 } 14 results.add(s2.isEmpty() ? -1 : s2.pop()); 15 } else if (op.equals("front")) { 16 if (s2.isEmpty()) { 17 while (!s1.isEmpty()) s2.push(s1.pop()); 18 } 19 results.add(s2.isEmpty() ? -1 : s2.peek()); 20 } else if (op.equals("isEmpty")) { 21 results.add((s1.isEmpty() && s2.isEmpty()) ? 1 : 0); 22 } else { 23 results.add(s1.size() + s2.size()); 24 } 25 } 26 int[] output = new int[results.size()]; 27 for (int i = 0; i < results.size(); i++) output[i] = results.get(i); 28 return output; 29 } 30}

Related Problems