225. Implement Stack using Queues
Problem Description
This problem asks you to implement a stack data structure using only two queues. A stack follows Last-In-First-Out (LIFO) principle, while a queue follows First-In-First-Out (FIFO) principle. The challenge is to simulate stack behavior using queue operations.
You need to implement a MyStack
class with four methods:
push(x)
: Add elementx
to the top of the stackpop()
: Remove and return the top element from the stacktop()
: Return the top element without removing itempty()
: Check if the stack is empty and returntrue
orfalse
The key constraint is that you can only use standard queue operations:
- Push to back (enqueue)
- Pop/peek from front (dequeue)
- Check size
- Check if empty
The solution uses two queues q1
and q2
. The clever approach maintains q1
such that its front element is always the stack's top element. During a push
operation:
- New element
x
is added toq2
- All elements from
q1
are transferred toq2
(behindx
) q1
andq2
are swapped
This ensures the most recently pushed element is always at the front of q1
, making pop()
and top()
operations simple - they just operate on the front of q1
. The time complexity for push
is O(n)
where n
is the number of elements, while pop()
, top()
, and empty()
are all O(1)
.
Intuition
The fundamental challenge is that queues and stacks have opposite access patterns. In a queue, the first element added is the first one removed (FIFO), while in a stack, the last element added is the first one removed (LIFO). To bridge this gap, we need to manipulate the order of elements.
The key insight is that we want the most recently added element to be accessible first. Since queues only allow removal from the front, we need to ensure the newest element is always at the front of our queue.
Think about what happens when we add a new element to a queue - it goes to the back. But we want it at the front for stack behavior. One way to achieve this is to reverse the entire queue after each insertion. However, we can't reverse a queue in-place using only standard queue operations.
This leads us to use two queues. When pushing a new element:
- We can add it to an empty queue
q2
first, making it the front element - Then transfer all existing elements from
q1
toq2
, placing them behind the new element - Finally swap the queues, so
q1
always contains our "stack" with the newest element at front
This rearrangement ensures that elements in q1
are ordered from newest to oldest (front to back), perfectly mimicking stack behavior. The beauty of this approach is that once elements are arranged this way during push
, all other operations become trivial - pop()
and top()
simply access the front of q1
, which is exactly where our stack's top element resides.
The trade-off is clear: we do more work during push
(moving all elements) to make pop
and top
extremely efficient.
Solution Approach
The implementation uses two queues q1
and q2
, where q1
serves as the main storage for stack elements and q2
acts as a temporary helper queue. The core strategy is to maintain q1
such that elements are ordered from newest to oldest (front to back).
Data Structure Initialization:
def __init__(self):
self.q1 = deque()
self.q2 = deque()
We initialize two empty queues using Python's deque
(double-ended queue) which supports efficient queue operations.
Push Operation - O(n):
def push(self, x: int) -> None:
self.q2.append(x)
while self.q1:
self.q2.append(self.q1.popleft())
self.q1, self.q2 = self.q2, self.q1
- Add the new element
x
to the empty queueq2
- Transfer all elements from
q1
toq2
one by one usingpopleft()
andappend()
- Swap the references of
q1
andq2
After this operation, q1
contains all elements with x
at the front, followed by the previously existing elements in their original order.
Pop Operation - O(1):
def pop(self) -> int:
return self.q1.popleft()
Simply remove and return the front element of q1
, which is the most recently pushed element (stack's top).
Top Operation - O(1):
def top(self) -> int:
return self.q1[0]
Access the front element of q1
without removing it. This gives us the stack's top element.
Empty Operation - O(1):
def empty(self) -> bool:
return len(self.q1) == 0
Check if q1
is empty by verifying its length is zero.
The pattern used here is essentially "reversing on insertion" - by rearranging elements during each push operation, we ensure that subsequent read operations (pop and top) are optimal. This is a classic time-complexity trade-off where we accept slower insertion to achieve faster retrieval.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a sequence of operations to see how the two-queue approach maintains stack behavior:
Initial State:
q1 = []
(empty)q2 = []
(empty)
Operation 1: push(3)
- Add 3 to
q2
:q2 = [3]
- Transfer all elements from
q1
toq2
:q1
is empty, so nothing to transfer - Swap queues:
q1 = [3]
,q2 = []
State after push(3): q1 = [3]
, q2 = []
Operation 2: push(7)
- Add 7 to
q2
:q2 = [7]
- Transfer all elements from
q1
toq2
:- Remove 3 from front of
q1
, add to back ofq2
- Now
q2 = [7, 3]
,q1 = []
- Remove 3 from front of
- Swap queues:
q1 = [7, 3]
,q2 = []
State after push(7): q1 = [7, 3]
, q2 = []
Operation 3: push(5)
- Add 5 to
q2
:q2 = [5]
- Transfer all elements from
q1
toq2
:- Remove 7 from front of
q1
, add to back ofq2
:q2 = [5, 7]
- Remove 3 from front of
q1
, add to back ofq2
:q2 = [5, 7, 3]
- Now
q1 = []
- Remove 7 from front of
- Swap queues:
q1 = [5, 7, 3]
,q2 = []
State after push(5): q1 = [5, 7, 3]
, q2 = []
Operation 4: top()
- Return front element of
q1
without removing: returns 5 - State remains:
q1 = [5, 7, 3]
,q2 = []
Operation 5: pop()
- Remove and return front element of
q1
: returns 5 - State after:
q1 = [7, 3]
,q2 = []
Operation 6: pop()
- Remove and return front element of
q1
: returns 7 - State after:
q1 = [3]
,q2 = []
Operation 7: empty()
- Check if
q1
is empty:len(q1) = 1
, returnsfalse
Operation 8: pop()
- Remove and return front element of
q1
: returns 3 - State after:
q1 = []
,q2 = []
Operation 9: empty()
- Check if
q1
is empty:len(q1) = 0
, returnstrue
Notice how the elements are popped in LIFO order (5, 7, 3) even though we're using queues. The key is that after each push, the newest element is positioned at the front of q1
, making it immediately accessible for pop and top operations.
Solution Implementation
1from collections import deque
2from typing import Optional
3
4
5class MyStack:
6 """
7 Implementation of a stack using two queues.
8 The stack follows LIFO (Last In First Out) principle.
9 """
10
11 def __init__(self):
12 """Initialize two empty queues for stack implementation."""
13 self.queue_main = deque() # Main queue that maintains stack order
14 self.queue_temp = deque() # Temporary queue used during push operation
15
16 def push(self, x: int) -> None:
17 """
18 Push element x onto stack.
19 Strategy: Add new element to temp queue, then move all elements from main queue
20 to temp queue, finally swap the queues to maintain stack order.
21
22 Args:
23 x: Integer element to be pushed onto the stack
24 """
25 # Add new element to temporary queue
26 self.queue_temp.append(x)
27
28 # Move all existing elements from main queue to temporary queue
29 while self.queue_main:
30 self.queue_temp.append(self.queue_main.popleft())
31
32 # Swap queues so main queue has elements in stack order (newest first)
33 self.queue_main, self.queue_temp = self.queue_temp, self.queue_main
34
35 def pop(self) -> int:
36 """
37 Remove and return the element on top of the stack.
38
39 Returns:
40 The top element of the stack
41 """
42 # Remove and return the first element (which is the last pushed element)
43 return self.queue_main.popleft()
44
45 def top(self) -> int:
46 """
47 Get the top element without removing it.
48
49 Returns:
50 The top element of the stack
51 """
52 # Return the first element without removing it
53 return self.queue_main[0]
54
55 def empty(self) -> bool:
56 """
57 Check whether the stack is empty.
58
59 Returns:
60 True if stack is empty, False otherwise
61 """
62 return len(self.queue_main) == 0
63
64
65# Your MyStack object will be instantiated and called as such:
66# obj = MyStack()
67# obj.push(x)
68# param_2 = obj.pop()
69# param_3 = obj.top()
70# param_4 = obj.empty()
71
1class MyStack {
2 // Primary queue that maintains elements in LIFO order (stack behavior)
3 private Deque<Integer> primaryQueue = new ArrayDeque<>();
4 // Auxiliary queue used temporarily during push operations
5 private Deque<Integer> auxiliaryQueue = new ArrayDeque<>();
6
7 /**
8 * Initialize the stack using two queues
9 */
10 public MyStack() {
11 }
12
13 /**
14 * Push element x onto stack
15 * Time Complexity: O(n) where n is the number of elements in the stack
16 *
17 * @param x the element to be pushed
18 */
19 public void push(int x) {
20 // Add the new element to the auxiliary queue
21 auxiliaryQueue.offer(x);
22
23 // Move all elements from primary queue to auxiliary queue
24 // This ensures the new element is at the front after swapping
25 while (!primaryQueue.isEmpty()) {
26 auxiliaryQueue.offer(primaryQueue.poll());
27 }
28
29 // Swap the references of the two queues
30 // After swap, primary queue has elements in LIFO order
31 Deque<Integer> temp = primaryQueue;
32 primaryQueue = auxiliaryQueue;
33 auxiliaryQueue = temp;
34 }
35
36 /**
37 * Remove and return the element on top of the stack
38 * Time Complexity: O(1)
39 *
40 * @return the top element of the stack
41 */
42 public int pop() {
43 return primaryQueue.poll();
44 }
45
46 /**
47 * Get the top element without removing it
48 * Time Complexity: O(1)
49 *
50 * @return the top element of the stack
51 */
52 public int top() {
53 return primaryQueue.peek();
54 }
55
56 /**
57 * Check whether the stack is empty
58 * Time Complexity: O(1)
59 *
60 * @return true if the stack is empty, false otherwise
61 */
62 public boolean empty() {
63 return primaryQueue.isEmpty();
64 }
65}
66
67/**
68 * Your MyStack object will be instantiated and called as such:
69 * MyStack obj = new MyStack();
70 * obj.push(x);
71 * int param_2 = obj.pop();
72 * int param_3 = obj.top();
73 * boolean param_4 = obj.empty();
74 */
75
1class MyStack {
2public:
3 MyStack() {
4 // Constructor - no initialization needed as queues are empty by default
5 }
6
7 void push(int x) {
8 // Push new element to auxiliary queue
9 auxiliary_queue.push(x);
10
11 // Transfer all elements from main queue to auxiliary queue
12 // This ensures the newly added element is at the front
13 while (!main_queue.empty()) {
14 auxiliary_queue.push(main_queue.front());
15 main_queue.pop();
16 }
17
18 // Swap the queues so main_queue has elements in LIFO order
19 swap(main_queue, auxiliary_queue);
20 }
21
22 int pop() {
23 // Remove and return the front element (which is the stack's top)
24 int top_element = main_queue.front();
25 main_queue.pop();
26 return top_element;
27 }
28
29 int top() {
30 // Return the front element without removing it
31 return main_queue.front();
32 }
33
34 bool empty() {
35 // Check if the stack is empty
36 return main_queue.empty();
37 }
38
39private:
40 queue<int> main_queue; // Primary queue maintaining stack order (LIFO)
41 queue<int> auxiliary_queue; // Helper queue used during push operation
42};
43
44/**
45 * Your MyStack object will be instantiated and called as such:
46 * MyStack* obj = new MyStack();
47 * obj->push(x);
48 * int param_2 = obj->pop();
49 * int param_3 = obj->top();
50 * bool param_4 = obj->empty();
51 */
52
1// Two queues to implement stack functionality
2let q1: number[] = [];
3let q2: number[] = [];
4
5/**
6 * Pushes element x to the top of the stack
7 * Time Complexity: O(n) where n is the number of elements in the stack
8 * Space Complexity: O(n) for temporary storage in q2
9 * @param x - The element to be pushed onto the stack
10 */
11function push(x: number): void {
12 // Add new element to the auxiliary queue
13 q2.push(x);
14
15 // Move all elements from main queue to auxiliary queue
16 // This ensures the new element will be at the front after swapping
17 while (q1.length > 0) {
18 q2.push(q1.shift()!);
19 }
20
21 // Swap the queues - q2 becomes the main queue with correct LIFO order
22 [q1, q2] = [q2, q1];
23}
24
25/**
26 * Removes and returns the element on the top of the stack
27 * Time Complexity: O(1)
28 * @returns The top element of the stack
29 */
30function pop(): number {
31 // Remove and return the first element (which represents the stack top)
32 return q1.shift()!;
33}
34
35/**
36 * Returns the element on the top of the stack without removing it
37 * Time Complexity: O(1)
38 * @returns The top element of the stack
39 */
40function top(): number {
41 // Return the first element without removing it
42 return q1[0];
43}
44
45/**
46 * Checks whether the stack is empty
47 * Time Complexity: O(1)
48 * @returns True if the stack is empty, false otherwise
49 */
50function empty(): boolean {
51 return q1.length === 0;
52}
53
Time and Space Complexity
Time Complexity:
push(x)
:O(n)
wheren
is the number of elements currently in the stack. This is because we need to transfer all elements fromq1
toq2
after adding the new element, which requiresn
dequeue and enqueue operations.pop()
:O(1)
since we only perform a single dequeue operation fromq1
.top()
:O(1)
since we only access the first element ofq1
directly.empty()
:O(1)
since we only check the length ofq1
.
Space Complexity:
O(n)
where n
is the number of elements in the stack. At any given time, all elements are stored in either q1
or temporarily in both queues during the push
operation. The maximum space used is when all n
elements are stored in q1
, plus the temporary space in q2
during transfers. However, since we swap the references rather than copying, the overall space complexity remains O(n)
.
Common Pitfalls
1. Forgetting to Swap Queues After Push Operation
A common mistake is transferring elements from q1
to q2
but forgetting to swap the queue references afterward. This breaks the invariant that q1
should always contain the stack elements.
Incorrect Implementation:
def push(self, x: int) -> None:
self.q2.append(x)
while self.q1:
self.q2.append(self.q1.popleft())
# Missing swap! q1 is now empty and q2 has all elements
Correct Implementation:
def push(self, x: int) -> None:
self.q2.append(x)
while self.q1:
self.q2.append(self.q1.popleft())
self.q1, self.q2 = self.q2, self.q1 # Critical swap operation
2. Using Wrong Queue Order During Push
Another pitfall is adding the new element after transferring existing elements, which results in incorrect stack order.
Incorrect Implementation:
def push(self, x: int) -> None:
while self.q1:
self.q2.append(self.q1.popleft())
self.q2.append(x) # Wrong! x should be first, not last
self.q1, self.q2 = self.q2, self.q1
Correct Implementation:
def push(self, x: int) -> None:
self.q2.append(x) # Add new element first
while self.q1:
self.q2.append(self.q1.popleft()) # Then add existing elements
self.q1, self.q2 = self.q2, self.q1
3. Accessing Empty Queue in top() Method
Calling top()
on an empty stack will raise an IndexError when trying to access q1[0]
.
Vulnerable Implementation:
def top(self) -> int:
return self.q1[0] # Crashes if q1 is empty
Defensive Implementation:
def top(self) -> int:
if not self.q1:
raise IndexError("top from empty stack")
return self.q1[0]
4. Memory Leak with Queue References
Not properly clearing q2
after swapping can lead to unnecessary memory usage if the implementation is modified later.
Better Practice:
def push(self, x: int) -> None:
self.q2.append(x)
while self.q1:
self.q2.append(self.q1.popleft())
self.q1, self.q2 = self.q2, self.q1
# q2 is now referencing the old q1 which is already empty - no issue
# But explicitly clearing can prevent bugs if code is modified later
self.q2.clear() # Optional but safer
5. Using List Instead of Deque
Using Python's list with pop(0)
instead of deque with popleft()
results in O(n) time complexity for dequeue operations.
Inefficient Implementation:
def __init__(self):
self.q1 = [] # Using list
self.q2 = []
def pop(self) -> int:
return self.q1.pop(0) # O(n) operation with list!
Efficient Implementation:
from collections import deque
def __init__(self):
self.q1 = deque() # Using deque
self.q2 = deque()
def pop(self) -> int:
return self.q1.popleft() # O(1) operation with deque
Which of the following uses divide and conquer strategy?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!