Facebook Pixel

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 element x to the top of the stack
  • pop(): Remove and return the top element from the stack
  • top(): Return the top element without removing it
  • empty(): Check if the stack is empty and return true or false

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:

  1. New element x is added to q2
  2. All elements from q1 are transferred to q2 (behind x)
  3. q1 and q2 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).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 to q2, 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
  1. Add the new element x to the empty queue q2
  2. Transfer all elements from q1 to q2 one by one using popleft() and append()
  3. Swap the references of q1 and q2

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 Evaluator

Example 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)

  1. Add 3 to q2: q2 = [3]
  2. Transfer all elements from q1 to q2: q1 is empty, so nothing to transfer
  3. Swap queues: q1 = [3], q2 = []

State after push(3): q1 = [3], q2 = []

Operation 2: push(7)

  1. Add 7 to q2: q2 = [7]
  2. Transfer all elements from q1 to q2:
    • Remove 3 from front of q1, add to back of q2
    • Now q2 = [7, 3], q1 = []
  3. Swap queues: q1 = [7, 3], q2 = []

State after push(7): q1 = [7, 3], q2 = []

Operation 3: push(5)

  1. Add 5 to q2: q2 = [5]
  2. Transfer all elements from q1 to q2:
    • Remove 7 from front of q1, add to back of q2: q2 = [5, 7]
    • Remove 3 from front of q1, add to back of q2: q2 = [5, 7, 3]
    • Now q1 = []
  3. 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, returns false

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, returns true

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) where n is the number of elements currently in the stack. This is because we need to transfer all elements from q1 to q2 after adding the new element, which requires n dequeue and enqueue operations.
  • pop(): O(1) since we only perform a single dequeue operation from q1.
  • top(): O(1) since we only access the first element of q1 directly.
  • empty(): O(1) since we only check the length of q1.

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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More