{x}
blog image

Implement Stack using Queues

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() Returns true if the stack is empty, false otherwise.

Notes:

  • You must use only standard operations of a queue, which means that only push to back, peek/pop from front, size and is empty operations are valid.
  • Depending on your language, the queue may not be supported natively. You may simulate a queue using a list or deque (double-ended queue) as long as you use only a queue's standard operations.

 

Example 1:

Input
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
Output
[null, null, null, 2, 2, false]

Explanation
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // return 2
myStack.pop(); // return 2
myStack.empty(); // return False

 

Constraints:

  • 1 <= x <= 9
  • At most 100 calls will be made to push, pop, top, and empty.
  • All the calls to pop and top are valid.

 

Follow-up: Can you implement the stack using only one queue?

Solution Explanation: Implementing a Stack using Queues

This problem challenges us to implement a stack data structure using only queues. A stack follows the Last-In-First-Out (LIFO) principle, while a queue follows the First-In-First-Out (FIFO) principle. The solution leverages the properties of queues to mimic stack behavior.

Approach: Using Two Queues

The most straightforward approach involves using two queues, q1 and q2. q1 primarily holds the stack's elements. q2 acts as a temporary queue to assist in performing stack operations efficiently.

Operations:

  • push(x): To add an element x to the stack (top), we enqueue x into q2. Then, we dequeue all elements from q1 and enqueue them into q2. Finally, we swap the references of q1 and q2 so that q1 now contains all elements with x at the end (top of the stack). This ensures the LIFO order.

  • pop(): We simply dequeue and return the front element of q1 (the top of the stack).

  • top(): We return the front element of q1 (the top of the stack) without removing it.

  • empty(): We check if q1 is empty to determine if the stack is empty.

Time and Space Complexity Analysis

  • Time Complexity:

    • push(x): O(n), where n is the number of elements in the stack. This is due to the potential need to move all elements from one queue to another.
    • pop(), top(), empty(): O(1). These operations involve only accessing or removing the front element of a queue, which is a constant-time operation.
  • Space Complexity: O(n). We use two queues, and in the worst case, both queues will hold all the stack elements.

Code Examples (Python and Java)

The code examples below demonstrate the implementation using Python and Java. The logic remains consistent across different languages, with minor syntactic differences.

Python:

from collections import deque
 
class MyStack:
    def __init__(self):
        self.q1 = deque()
        self.q2 = deque()
 
    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
 
    def pop(self) -> int:
        return self.q1.popleft()
 
    def top(self) -> int:
        return self.q1[0]
 
    def empty(self) -> bool:
        return len(self.q1) == 0

Java:

import java.util.ArrayDeque;
import java.util.Deque;
 
class MyStack {
    private Deque<Integer> q1 = new ArrayDeque<>();
    private Deque<Integer> q2 = new ArrayDeque<>();
 
    public MyStack() {}
 
    public void push(int x) {
        q2.offer(x);
        while (!q1.isEmpty()) {
            q2.offer(q1.poll());
        }
        Deque<Integer> temp = q1;
        q1 = q2;
        q2 = temp;
    }
 
    public int pop() {
        return q1.poll();
    }
 
    public int top() {
        return q1.peek();
    }
 
    public boolean empty() {
        return q1.isEmpty();
    }
}

Other languages (C++, Go, TypeScript, Rust) follow a very similar structure, adapting to the specific language's queue implementation and syntax. The core logic of using two queues to simulate a stack remains consistent across all implementations.