Queue Using Two Stacks in DSA C# - 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.
public class QueueUsingStacks {
private Stack<int> stack1 = new Stack<int>();
private Stack<int> stack2 = new Stack<int>();
public void Enqueue(int x) {
stack1.Push(x);
}
public int Dequeue() {
if (stack2.Count == 0) {
while (stack1.Count > 0) {
stack2.Push(stack1.Pop());
}
}
return stack2.Pop();
}
}
This code adds items to one stack and removes items from another, moving items only when needed.
Identify the loops, recursion, array traversals 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.
Adding items (enqueue) always takes the same small time per item.
Removing items (dequeue) sometimes takes longer when moving items between stacks, but this happens rarely.
| Input Size (n) | Approx. Operations per Dequeue |
|---|---|
| 10 | About 1 or 10 operations occasionally |
| 100 | About 1 or 100 operations occasionally |
| 1000 | About 1 or 1000 operations occasionally |
Pattern observation: Most dequeues are fast, but some take longer when stacks are moved. On average, time per operation stays steady.
Time Complexity: O(1) amortized
This means each operation takes constant time on average, even if some take longer occasionally.
[X] Wrong: "Each dequeue always takes time proportional to the number of items because of moving stacks."
[OK] Correct: Items move only once from stack1 to stack2, so the cost spreads out over many dequeues, making average time constant.
Understanding this helps you explain how clever use of data structures can keep operations fast 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
One stack reverses the order when pushing elements, and the second stack reverses it back when popping, maintaining FIFO.Final Answer:
Use two stacks to reverse the order twice to maintain queue order -> Option CQuick Check:
Two reversals keep FIFO order [OK]
- Thinking stacks store sorted elements
- Using stacks for separate data types
- Confusing enqueue and dequeue roles
5 into a stack named stack1 in C#?Solution
Step 1: Recall stack push method
In C#, theStack<T>class uses thePushmethod to add elements.Step 2: Check method names
AddandInsertare not stack methods;Enqueueis for queues.Final Answer:
stack1.Push(5); -> Option AQuick Check:
Stack push method = Push() [OK]
- Using Add() or Insert() which are not stack methods
- Confusing Enqueue() with Push()
- Using wrong method names
var queue = new QueueUsingTwoStacks(); queue.Enqueue(10); queue.Enqueue(20); queue.Enqueue(30); Console.WriteLine(queue.Dequeue()); queue.Enqueue(40); Console.WriteLine(queue.Dequeue());
Solution
Step 1: Enqueue elements 10, 20, 30
Elements are pushed into the first stack in order: 10, then 20, then 30.Step 2: Dequeue operation
On dequeue, elements are popped from the second stack after transferring from the first, so 10 is dequeued first.Step 3: Enqueue 40 and dequeue again
40 is pushed into the first stack. Next dequeue returns 20, the next in queue order.Final Answer:
10 20 -> Option BQuick Check:
FIFO order maintained: 10 then 20 [OK]
- Assuming dequeue returns last pushed element
- Not transferring elements between stacks correctly
- Mixing enqueue and dequeue order
public int Dequeue()
{
if (stack2.Count == 0)
{
while (stack1.Count > 0)
stack2.Push(stack1.Pop());
}
return stack1.Pop();
}Solution
Step 1: Understand dequeue logic
After transferring elements from stack1 to stack2, dequeue should pop from stack2 to maintain correct order.Step 2: Identify wrong pop call
The code incorrectly pops from stack1 instead of stack2 after transfer.Final Answer:
Should pop from stack2, not stack1, after transfer -> Option AQuick Check:
Pop from stack2 after transfer [OK]
- Popping from wrong stack after transfer
- Not checking if stacks are empty
- Using Peek() instead of Pop() for dequeue
Enqueue is O(1) time and Dequeue is amortized O(1). Which approach is best?Solution
Step 1: Understand enqueue O(1) requirement
Enqueue should just push to stack1 without extra operations for O(1) time.Step 2: Understand dequeue amortized O(1)
Dequeue transfers elements from stack1 to stack2 only when stack2 is empty, making transfers infrequent and amortized O(1).Step 3: Evaluate options
Push all elements to stack1 on enqueue; transfer to stack2 only when stack2 is empty during dequeue matches this approach; others do not maintain correct order or time complexity.Final Answer:
Push all elements to stack1 on enqueue; transfer to stack2 only when stack2 is empty during dequeue -> Option DQuick Check:
Lazy transfer on dequeue gives amortized O(1) [OK]
- Transferring on every enqueue causing O(n) enqueue
- Popping from wrong stack without transfer
- Alternating pushes breaking queue order
Ready to prove it?
Pass the test and this pattern moves unknown
