Queue Using Two Stacks in DSA Java - Time & Space Complexity
We want to understand how fast operations run when we build a queue using two stacks.
How does the time to add or remove items grow as we add more items?
Analyze the time complexity of the following code snippet.
class QueueUsingStacks {
Stack stack1 = new Stack<>();
Stack stack2 = new Stack<>();
void enqueue(int x) {
stack1.push(x);
}
int dequeue() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
This code adds items to one stack and removes items from another, moving items between stacks only when needed.
Look at the loops and stack operations that repeat.
- Primary operation: Moving all items from stack1 to stack2 when stack2 is empty during dequeue.
- How many times: Each item moves at most twice: once pushed to stack1, once popped and pushed to stack2, and once popped from stack2.
Adding items is always quick, but removing items sometimes takes longer when stacks move items.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 pushes and pops total |
| 100 | About 200 pushes and pops total |
| 1000 | About 2000 pushes and pops total |
Pattern observation: Each item is moved a fixed number of times, so total work grows linearly with input size.
Time Complexity: O(1) amortized per operation
This means on average, each add or remove takes constant time, even if some removals take longer sometimes.
[X] Wrong: "Each dequeue always takes time proportional to the number of items because of the loop."
[OK] Correct: The loop only runs when stack2 is empty, so most dequeues are fast. The cost spreads out over many operations.
Understanding this helps you explain how clever use of data structures can make operations efficient on average, a skill valued in problem solving.
"What if we moved items from stack1 to stack2 on every dequeue, not only when stack2 is empty? How would the time complexity change?"
Practice
Queue using two stacks?Solution
Step 1: Understand queue order
A queue follows First-In-First-Out (FIFO) order, meaning the first element added is the first removed.Step 2: Use two stacks to reverse order twice
Stacks are Last-In-First-Out (LIFO). Using two stacks, we reverse the order twice, restoring FIFO behavior.Final Answer:
Use two stacks to reverse the order twice to maintain queue order -> Option AQuick Check:
Two stacks reverse twice = queue order [OK]
- Thinking one stack alone can maintain queue order
- Assuming stacks keep elements sorted
- Confusing enqueue and dequeue operations
Solution
Step 1: Identify correct Java method signature
In Java, method signature includes access modifier, return type, method name, and parameters. Enqueue adds an element and returns nothing, so return type is void.Step 2: Check each option
public void enqueue(int x) has public access, void return type, method name enqueue, and int parameter. This is correct syntax.Final Answer:
public void enqueue(int x) -> Option DQuick Check:
Method signature with void return and int parameter [OK]
- Missing return type or wrong return type
- Incorrect parameter placement
- Syntax errors like misplaced braces
QueueUsingTwoStacks q = new QueueUsingTwoStacks(); q.enqueue(10); q.enqueue(20); q.enqueue(30); System.out.println(q.dequeue()); q.enqueue(40); System.out.println(q.dequeue());
Solution
Step 1: Trace enqueue operations
Elements enqueued in order: 10, 20, 30. Queue order is [10, 20, 30].Step 2: Dequeue and enqueue operations
First dequeue removes 10 (front). Then enqueue 40, queue is [20, 30, 40]. Second dequeue removes 20.Final Answer:
10 20 -> Option AQuick Check:
Dequeue order matches enqueue FIFO [OK]
- Assuming stack order instead of queue order
- Mixing up enqueue and dequeue sequence
- Forgetting to enqueue after dequeue
public int dequeue() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack1.pop();
}Solution
Step 1: Analyze dequeue logic
When stack2 is empty, elements are moved from stack1 to stack2 to reverse order. Dequeue should pop from stack2 to get the front element.Step 2: Identify error in return statement
Code returns stack1.pop(), which is incorrect. It should return stack2.pop() to dequeue correctly.Final Answer:
Returns from wrong stack; should pop from stack2 -> Option BQuick Check:
Dequeue pops from stack2 after transfer [OK]
- Popping from stack1 instead of stack2
- Not transferring elements when stack2 is empty
- Ignoring empty queue condition
enqueue is O(1) time and dequeue is amortized O(1). Which approach is best?Options:
A) Push new elements to stack1 on enqueue; on dequeue, if stack2 empty, move all from stack1 to stack2, then pop stack2.
B) Push new elements to stack2 on enqueue; on dequeue, pop from stack1.
C) Push new elements to stack1 on enqueue; on dequeue, pop from stack1 directly.
D) Push new elements to stack2 on enqueue; on dequeue, if stack1 empty, move all from stack2 to stack1, then pop stack1.
Solution
Step 1: Understand enqueue and dequeue costs
Enqueue should be O(1), so pushing to a stack directly is best. Dequeue can be amortized O(1) by transferring elements only when needed.Step 2: Identify correct approach
Push to stack1 on enqueue; transfer to stack2 on dequeue if empty pushes to stack1 on enqueue (O(1)). On dequeue, if stack2 is empty, transfer all from stack1 to stack2 (reversing order), then pop from stack2. This gives amortized O(1) dequeue.Final Answer:
Push to stack1 on enqueue; transfer to stack2 on dequeue if empty -> Option CQuick Check:
Enqueue O(1), dequeue amortized O(1) with two stacks [OK]
- Pushing to wrong stack on enqueue
- Popping from wrong stack on dequeue
- Not transferring elements when needed
Ready to prove it?
Pass the test and this pattern moves unknown
