232. Implement Queue using Stacks - Explanation

Problem Link

Description

Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (push, peek, pop, and empty).

Implement the MyQueue class:

  • void push(int x) Pushes element x to the back of the queue.
  • int pop() Removes the element from the front of the queue and returns it.
  • int peek() Returns the element at the front of the queue.
  • boolean empty() Returns true if the queue is empty, false otherwise.

Notes:

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

Example 1:

Input: ["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]

Output: [null, null, null, 1, 1, false]

Explanation:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

Constraints:

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

Follow-up: Can you implement the queue such that each operation is amortized O(1) time complexity? In other words, performing n operations will take overall O(n) time even if one of those operations may take longer.



Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Stacks - Understanding LIFO (Last-In-First-Out) operations including push, pop, and peek
  • Queues - Understanding FIFO (First-In-First-Out) behavior and why it differs from stacks
  • Amortized Analysis - Recognizing when operations have different worst-case vs average-case time complexities

1. Using Two Stacks (Brute Force)

Intuition

A stack is LIFO (last in, first out) while a queue is FIFO (first in, first out).
To simulate a queue, we need to reverse the order of elements.
By transferring all elements except the bottom one to a second stack, popping the bottom, and transferring back, we can access the first element.
This approach is simple but inefficient since every pop/peek requires moving all elements twice.

Algorithm

  1. For push: Simply push the element onto stack1.
  2. For pop: Move all elements except the last from stack1 to stack2, pop the remaining element from stack1, then move all elements back from stack2 to stack1.
  3. For peek: Same as pop, but instead of popping, just read the top element of stack1 after moving elements.
  4. For empty: Return true if stack1 is empty.
class MyQueue:

    def __init__(self):
        self.stack1 = []
        self.stack2 = []

    def push(self, x: int) -> None:
        self.stack1.append(x)

    def pop(self) -> int:
        while len(self.stack1) > 1:
            self.stack2.append(self.stack1.pop())
        res = self.stack1.pop()
        while self.stack2:
            self.stack1.append(self.stack2.pop())
        return res

    def peek(self) -> int:
        while len(self.stack1) > 1:
            self.stack2.append(self.stack1.pop())
        res = self.stack1[-1]
        while self.stack2:
            self.stack1.append(self.stack2.pop())
        return res

    def empty(self) -> bool:
        return not self.stack1

Time & Space Complexity

  • Time complexity:
    • O(1)O(1) time for initialization.
    • O(1)O(1) time for each push()push() and empty()empty() function calls.
    • O(n)O(n) time for each pop()pop() and peek()peek() function calls.
  • Space complexity: O(n)O(n)

2. Using Two Stacks (Amortized Complexity)

Intuition

Instead of moving elements back after each pop, we can keep them in the second stack.
stack1 handles incoming elements (push), while stack2 holds elements in reversed order for popping.
When stack2 is empty and we need to pop, we transfer all elements from stack1 to stack2 at once.
Each element is moved at most twice (once to stack2, once when popped), giving amortized O(1) per operation.

Algorithm

  1. For push: Push the element onto s1.
  2. For pop: If s2 is empty, transfer all elements from s1 to s2. Then pop from s2.
  3. For peek: If s2 is empty, transfer all elements from s1 to s2. Then return the top of s2.
  4. For empty: Return true if both s1 and s2 are empty.
class MyQueue:

    def __init__(self):
        self.s1 = []
        self.s2 = []

    def push(self, x: int) -> None:
        self.s1.append(x)

    def pop(self) -> int:
        if not self.s2:
            while self.s1:
                self.s2.append(self.s1.pop())
        return self.s2.pop()

    def peek(self) -> int:
        if not self.s2:
            while self.s1:
                self.s2.append(self.s1.pop())
        return self.s2[-1]

    def empty(self) -> bool:
        return max(len(self.s1), len(self.s2)) == 0

Time & Space Complexity

  • Time complexity:
    • O(1)O(1) time for initialization.
    • O(1)O(1) time for each push()push() and empty()empty() function calls.
    • O(1)O(1) amortized time for each pop()pop() and peek()peek() function calls.
  • Space complexity: O(n)O(n)

Common Pitfalls

Checking Only One Stack for Empty

In the optimized two-stack approach, the empty() method must check if both stacks are empty, not just one. A common mistake is returning s1.isEmpty() without considering s2. Elements may exist in s2 ready to be popped even when s1 is empty. The correct check is s1.isEmpty() && s2.isEmpty().

Transferring Elements Back After Every Pop

In the brute force approach, forgetting to transfer elements back from stack2 to stack1 after a pop operation breaks subsequent operations. However, in the optimized approach, the opposite mistake is problematic: unnecessarily transferring elements back defeats the amortized O(1) complexity. The key insight is that elements in s2 are already in the correct order for popping and should remain there until exhausted.