Implement a Stack Using Two Queues

Solve this Problem
Easy20–25 min
Topics
Companies
Practice:LeetCode ↗GFG ↗
Implement a LIFO stackLIFOLast In, First Out — the most recently pushed element is always the first one popped. using only queue operationsQueue OperationsEnqueue (add to the back), dequeue (remove from the front), and peek the front — a queue is FIFO (First In, First Out), the opposite order from a stack. — enqueue at the back, dequeue from the front, peek the front — supporting push(v), pop(), top(), isEmpty(), and size(). As with the array and linked-list versions, this takes a fixed sequence of operations and replays them, returning the result of every pop, top, isEmpty, and size call as an array. A queue naturally hands back its oldest element first — the opposite of what a stack needs. Two classic techniques bridge that gap, and they're complementary rather than one strictly beating the other: rotate the queue after every push so the newest value ends up at the front (cheap pop, expensive push), or drain the queue down to just its last element whenever something needs to come off the top (cheap push, expensive pop). Neither approach avoids the reordering work a real stack never needs in the first place — they just disagree about which operation should pay for it.

Test Case 1:

Input:ops = [push, push, push, pop, pop, top], args = [5, 10, 15, -, -, -]
Output:[15, 10, 5]
Explanation:push 5, 10, 15; pop returns 15 (last in), pop returns 10, top peeks at 5 without removing it.

Test Case 2:

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

Test Case 3:

Input:ops = [push, push, size, pop, size], args = [1, 2, -, -, -]
Output:[2, 2, 1]
Explanation:After two pushes, size→2. pop removes the top (2) and returns it. size→1 afterward.

Constraints

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

Try the Dry Run

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

Approach & Solutions

Push Costly — Rotate the Queue After Every Push

Good

Keep everything in a single queue. To push a value, enqueue it at the back like normal, then rotate the whole queue — dequeue from the front and immediately enqueue it at the back — exactly (size - 1) times. That walks every older element around to the back, one at a time, leaving the brand-new value sitting at the front. From then on, the queue's front is always the most recently pushed value, so pop and top are trivial O(1) reads from the front. The cost is pushed entirely onto push itself: it does O(size) work every time.

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

Pop Costly — Two Queues, Transfer on Pop

Optimal

The complementary trade-off: keep push cheap instead of pop. Every push is a plain enqueue onto q1 — O(1), no rotation. All the work happens when something needs to come off the top: drain every element except the last from q1 into a second queue q2 (dequeue from q1, enqueue into q2), leaving only the most-recently-pushed value in q1 — dequeue that one as the answer, then swap the roles of q1 and q2 so the next push continues on the (now-repopulated) main queue. Tracking the most recent value in a separate variable, updated on every push, makes top() an O(1) read too instead of repeating the drain-and-restore dance.

TimeO(1) per push, O(n) per pop
SpaceO(n)
1class Solution { 2 public int[] stackOpsFromQueues(String[] ops, int[] args) { 3 Queue<Integer> q1 = new LinkedList<>(); 4 Queue<Integer> q2 = new LinkedList<>(); 5 int topVal = -1; 6 List<Integer> results = new ArrayList<>(); 7 for (int i = 0; i < ops.length; i++) { 8 String op = ops[i]; 9 if (op.equals("push")) { 10 q1.add(args[i]); 11 topVal = args[i]; 12 } else if (op.equals("pop")) { 13 if (q1.isEmpty()) { 14 results.add(-1); 15 } else { 16 while (q1.size() > 1) { 17 topVal = q1.poll(); 18 q2.add(topVal); 19 } 20 results.add(q1.poll()); 21 Queue<Integer> tmp = q1; 22 q1 = q2; 23 q2 = tmp; 24 } 25 } else if (op.equals("top")) { 26 results.add(q1.isEmpty() ? -1 : topVal); 27 } else if (op.equals("isEmpty")) { 28 results.add(q1.isEmpty() ? 1 : 0); 29 } else { 30 results.add(q1.size()); 31 } 32 } 33 int[] output = new int[results.size()]; 34 for (int i = 0; i < results.size(); i++) output[i] = results.get(i); 35 return output; 36 } 37}

Related Problems