114. Implement Stack using Queues
🔗 LeetCode Problem: 225. Implement Stack using Queues
📊 Difficulty: Easy
🏷️ Topics: Stack, Queue, Design
Problem Statement
Implement a last-in-first-out (LIFO) stack using only two queues. The implemented stack should support all the functions of a normal stack (push, top, pop, and empty).
Implement the MyStack class:
void push(int x)Pushes element x to the top of the stack.int pop()Removes the element on the top of the stack and returns it.int top()Returns the element on the top of the stack.boolean empty()Returnstrueif the stack is empty,falseotherwise.
Notes: - You must use only standard operations of a queue. - You can use two queues.
Example:
Input:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
Output:
[null, null, null, 2, 2, false]
🌟 Understanding the Problem
The Behavior Difference
Stack (LIFO):
Plates stacked on table:
push(1) → [1]
push(2) → [2, 1] (2 on top)
push(3) → [3, 2, 1] (3 on top)
pop() → get 3 (last in, first out)
Queue (FIFO):
Line of people:
add(1) → [1]
add(2) → [1, 2]
add(3) → [1, 2, 3]
front → back
remove() → get 1 (first in, first out)
The Challenge: Queue gives FIRST, we need LAST!
💡 Building Intuition - Two Different Ideas
Idea 1: What If We Had a Helper Queue?
Think about this scenario:
We have queue with: [1, 2, 3]
front → back
We want 3 (at the back).
What if we transfer all EXCEPT the last one to another queue?
Main queue: [1, 2, 3]
Helper queue: []
Transfer 1:
Main: [2, 3]
Helper: [1]
Transfer 2:
Main: [3]
Helper: [1, 2]
Now main has only 3! Remove it!
Main: []
Helper: [1, 2]
Then swap: Make helper become main
Main: [1, 2]
Helper: []
We got 3 (the last element)!
The Pattern:
To pop:
1. Transfer all except last to helper queue
2. Pop the last element from main
3. Swap the queues
This way we can get the LAST element!
Idea 2: What If We Rotate After Each Push?
Another way to think:
After adding element, what if we rotate the SAME queue?
Add 2 to [1]:
Queue: [1, 2]
Rotate:
Remove 1, add back → [2, 1]
Now 2 (newest) is at front!
For pop:
Just remove from front → get 2 ✓
We only need ONE queue!
Let me teach you BOTH approaches properly!
🎯 Approach 1: Two Queues (Original Problem)
The Intuition
When do we need the second queue?
When we want to pop!
We need to get to the LAST element:
→ Transfer all elements EXCEPT last to helper
→ Pop the last one
→ Swap queues
The helper queue is like a "parking lot" for elements
while we get to the one we want.
The Strategy
push(x):
Just add to queue1
(No extra work needed!)
pop():
Step 1: Transfer n-1 elements from queue1 to queue2
Step 2: Pop the last element from queue1
Step 3: Swap queue1 and queue2
top():
Similar to pop, but don't remove the element
(Transfer, peek, transfer back)
Visual Example
══════════════════════════════════════════════════════════
Setup: push(1), push(2), push(3)
══════════════════════════════════════════════════════════
After pushes:
queue1: [1, 2, 3]
queue2: []
══════════════════════════════════════════════════════════
Now: pop() - Want to get 3
══════════════════════════════════════════════════════════
Step 1: Transfer all except last from queue1 to queue2
Transfer 1:
queue1: [2, 3]
queue2: [1]
Transfer 2:
queue1: [3]
queue2: [1, 2]
Step 2: Pop from queue1
result = 3
queue1: []
queue2: [1, 2]
Step 3: Swap queues
queue1: [1, 2] (was queue2)
queue2: [] (was queue1)
Return: 3 ✓
Now queue1 has [1, 2] ready for next operation!
Implementation (Two Queues)
class MyStack {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
queue1.offer(x);
}
public int pop() {
// Transfer all except last to queue2
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
// Get the last element
int result = queue1.poll();
// Swap queues
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return result;
}
public int top() {
// Transfer all except last to queue2
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
// Peek at the last element
int result = queue1.peek();
// Transfer this element too
queue2.offer(queue1.poll());
// Swap queues
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return result;
}
public boolean empty() {
return queue1.isEmpty();
}
}
⏰ Time Complexity:
- push(): O(1) - Just add to queue
- pop(): O(n) - Transfer n-1 elements
- top(): O(n) - Transfer n elements
- empty(): O(1)
🎯 Approach 2: One Queue (Optimization)
The Realization
Do we really need TWO queues?
In Approach 1, we transfer elements from queue1 to queue2.
But what if... we transfer back to THE SAME queue?
Original: [1, 2, 3]
front → back
Remove 1, add to back: [2, 3, 1]
Remove 2, add to back: [3, 1, 2]
Now 3 is at front!
We rotated within the SAME queue!
We don't need a second queue!
The Strategy
push(x):
Step 1: Add x to queue
Step 2: Rotate (size - 1) times
(brings x to front)
pop():
Just remove from front
top():
Just peek at front
Visual Example
══════════════════════════════════════════════════════════
push(1):
══════════════════════════════════════════════════════════
Add 1: [1]
Rotate 0 times (size-1 = 0)
Queue: [1]
══════════════════════════════════════════════════════════
push(2):
══════════════════════════════════════════════════════════
Add 2: [1, 2]
Rotate 1 time (size-1 = 1):
Remove 1, add back: [2, 1]
Queue: [2, 1]
↑
newest at front!
══════════════════════════════════════════════════════════
push(3):
══════════════════════════════════════════════════════════
Add 3: [2, 1, 3]
Rotate 2 times (size-1 = 2):
Remove 2, add back: [1, 3, 2]
Remove 1, add back: [3, 2, 1]
Queue: [3, 2, 1]
↑
newest at front!
══════════════════════════════════════════════════════════
pop():
══════════════════════════════════════════════════════════
Just remove from front: get 3 ✓
Queue: [2, 1]
Implementation (One Queue)
class MyStack {
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
// Add new element
queue.offer(x);
// Rotate to bring x to front
int size = queue.size();
for (int i = 0; i < size - 1; i++) {
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
⏰ Time Complexity:
- push(): O(n) - Rotate n-1 elements
- pop(): O(1) - Just remove from front
- top(): O(1) - Just peek at front
- empty(): O(1)
🔍 Detailed Dry Run - Two Queue Approach
Operations: push(1), push(2), push(3), pop(), top()
═══════════════════════════════════════════════════════════
Operation: push(1)
═══════════════════════════════════════════════════════════
queue1.offer(1)
State:
queue1: [1]
queue2: []
═══════════════════════════════════════════════════════════
Operation: push(2)
═══════════════════════════════════════════════════════════
queue1.offer(2)
State:
queue1: [1, 2]
queue2: []
═══════════════════════════════════════════════════════════
Operation: push(3)
═══════════════════════════════════════════════════════════
queue1.offer(3)
State:
queue1: [1, 2, 3]
queue2: []
Stack view:
3 ← top
2
1
═══════════════════════════════════════════════════════════
Operation: pop() - Want 3
═══════════════════════════════════════════════════════════
Step 1: Transfer all except last to queue2
queue1.size() = 3
Transfer while size > 1:
Transfer 1:
queue2.offer(queue1.poll())
queue1: [2, 3]
queue2: [1]
Transfer 2:
queue2.offer(queue1.poll())
queue1: [3]
queue2: [1, 2]
Step 2: Pop from queue1
result = queue1.poll() = 3
queue1: []
queue2: [1, 2]
Step 3: Swap queues
temp = queue1
queue1 = queue2 → queue1: [1, 2]
queue2 = temp → queue2: []
Return: 3 ✓
State after pop:
queue1: [1, 2]
queue2: []
Stack view:
2 ← top
1
═══════════════════════════════════════════════════════════
Operation: top() - Want 2 (don't remove)
═══════════════════════════════════════════════════════════
Step 1: Transfer all except last to queue2
Transfer 1:
queue1: [2]
queue2: [1]
Step 2: Peek at last element
result = queue1.peek() = 2
Step 3: Transfer this element too (don't remove it!)
queue2.offer(queue1.poll())
queue1: []
queue2: [1, 2]
Step 4: Swap queues
queue1: [1, 2]
queue2: []
Return: 2 ✓
State:
queue1: [1, 2] (unchanged from before)
queue2: []
🎯 Comparing Both Approaches
Which One to Use?
Two Queues One Queue
(Original) (Optimized)
─────────────────────────────────────────────────
push() O(1) O(n)
pop() O(n) O(1)
top() O(n) O(1)
Space 2 queues 1 queue
Code More complex Simpler
Best for Frequent push Frequent pop
Problem asks: Two queues ✓ Works too
When to Use Each?
Use Two Queues when:
- Problem specifically asks for two queues
- You push frequently, pop rarely
- Example: Building a stack for later processing
push(1), push(2), push(3), ... push(100) ← All O(1)
pop() once ← One O(n) operation
Total: 100 × O(1) + 1 × O(n) = O(n)
Use One Queue when:
- You want simpler code
- You pop/top frequently
- Example: Undo functionality
push(action1) ← O(n)
pop() (undo) ← O(1)
push(action2) ← O(n)
pop() (undo) ← O(1)
Simpler to understand and implement
⚠️ Common Mistakes
Mistake 1: Not swapping queues (Two-queue approach)
// ❌ WRONG
public int pop() {
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
return queue1.poll();
// Forgot to swap! Next operation will fail
}
// ✓ CORRECT - Always swap
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
Mistake 2: Wrong number of rotations (One-queue approach)
// ❌ WRONG - Rotating size times
for (int i = 0; i < queue.size(); i++) {
queue.offer(queue.poll());
}
// Brings element back to same position!
// ✓ CORRECT - Rotate (size - 1) times
int size = queue.size(); // Store first!
for (int i = 0; i < size - 1; i++) {
queue.offer(queue.poll());
}
Mistake 3: Using queue.size() in loop condition
// ❌ WRONG - Size changes during loop
for (int i = 0; i < queue.size() - 1; i++) {
queue.offer(queue.poll());
}
// ✓ CORRECT - Store size before loop
int size = queue.size();
for (int i = 0; i < size - 1; i++) {
queue.offer(queue.poll());
}
🎯 The Core Insights
For Two Queues:
"Use helper queue as temporary storage.
Transfer all except target element.
Then swap queues."
When you pop:
Main → Helper (transfer all except last)
Pop last from Main
Swap (Helper becomes Main)
For One Queue:
"Rotate within the same queue.
Keep newest always at front."
When you push:
Add new element
Rotate to bring it to front
🧠 Interview Strategy
If asked for TWO queues:
"I'll use two queues. One is main, one is helper.
For push: Just add to main queue - O(1)
For pop:
Transfer all except last to helper - O(n)
Pop the last element
Swap queues
This way push is fast, pop is slower.
Good when we push frequently."
If asked for optimal:
"I can optimize to one queue.
After each push, rotate the queue
to bring the new element to front.
Then pop/top are just O(1) operations.
Trade-off: Push becomes O(n), but pop is O(1).
Better when we pop frequently."
📝 Quick Revision
Two-Queue Approach: - push: O(1) - just add - pop: O(n) - transfer, pop, swap - Best when: frequent push, rare pop
One-Queue Approach: - push: O(n) - add, then rotate (size-1) times - pop: O(1) - just remove from front - Best when: frequent pop, rare push
import java.util.LinkedList;
import java.util.Queue;
// // Approach 1 - using single queue.
// class MyStack {
// Queue<Integer> queue;
// public MyStack() {
// queue = new LinkedList<>();
// }
// public void push(int x) {
// // push(1) => [1]
// // push(2) => [1, 2] => make it [2, 1]
// // push(3) => [2, 1, 3] => make it [1, 3, 2] => again make it [3, 2, 1]
// // We can actually do at pop side also. It depends on use case.
// // If pop or top operations are heavy, do it at push side all this.
// // If pop operations are less, do it at pop side.
// queue.add(x);
// int size = queue.size();
// for(int i = 0; i < size - 1; i++) {
// queue.add(queue.poll());
// }
// }
// public int pop() {
// return queue.poll();
// }
// public int top() {
// return queue.peek();
// }
// public boolean empty() {
// return queue.isEmpty();
// }
// }
// Approach 2 - using 2 queues as asked in question - little tricky
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
// simply push
queue1.add(x);
}
public int pop() {
// make use of 2nd queue here.
// Step 1: transfer all elements from Q1 to Q2 except the last one.
int size = queue1.size();
for(int i = 0; i < size - 1; i++) {
queue2.add(queue1.poll());
}
// Step 2: remove the required element from Q1.
int top = queue1.poll();
// Step 3: change the references (SWAP) for next push or pop.
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return top;
}
public int top() {
// same logic as of pop except that we do not remove the element from Q1.
// we save that and push to Q2.
int size = queue1.size();
for(int i = 0; i < size - 1; i++) {
queue2.add(queue1.poll());
}
// Step 2: save the required element from Q1.
int top = queue1.poll();
// Step 3: push the saved element to Q2.
queue2.add(top);
// Step 3: change the references (SWAP) for next push or pop.
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return top;
}
public boolean empty() {
return queue1.isEmpty();
}
}
class Solution {
public static void main(String[] args) {
Solution s = new Solution();
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.push(3);
System.out.println(myStack.top());
// System.out.println(myStack.pop());
// System.out.println(myStack.empty());
}
}
Both work! Choose based on usage pattern.
🎉 Summary
The Problem: Stack (LIFO) using Queue (FIFO)
Two Solutions: 1. Two Queues: Transfer to helper, swap 2. One Queue: Rotate to maintain order
Key Insight: - Two queues: Helper for temporary storage - One queue: Rotation transforms FIFO → LIFO
Choose based on: What operation is more frequent?
Happy practicing! 🎯
Note: Problem originally asks for TWO queues, so that's the expected solution. But knowing the one-queue optimization shows deeper understanding! 💪✨