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:
push to top, peek/pop from top, size, and is empty operations are valid.["MyQueue", "push", "push", "peek", "pop", "empty"] [[], [1], [2], [], [], []]
1 <= x <= 9100 calls will be made to push, pop, peek, and empty.pop and peek are valid.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.
Each pop and peek call transfers all n elements to stack2 and then back again, performing 2n stack operations. push and empty are .
All n elements are distributed across the two stacks at any time; no extra space is allocated during transfers.
This approach uses two stacks (stack1 and stack2) to simulate a queue, treating stack1 as the single source of truth at all times. For pop and peek, all elements are first transferred from stack1 to stack2 (which reverses the order, bringing the oldest element to the top), the front value is retrieved, and then every remaining element is transferred back to stack1. This ensures the queue invariant is always fully restored after each read operation, at the cost of doing double the transfer work.
A single pop or peek can cost when out_stack is empty and all n elements must be transferred, but since each element crosses from in_stack to out_stack at most once total, the amortized cost across n operations is . push and empty are always .
All n elements are distributed across the two stacks at any time; no extra space is allocated during transfers.
This approach also uses two stacks but applies lazy transfer — elements are only moved from in_stack to out_stack when out_stack is completely empty. New elements are always pushed to in_stack, while pop and peek serve exclusively from out_stack, triggering a bulk transfer only when needed. Because each element is moved from in_stack to out_stack at most once across its lifetime, the amortized cost per operation drops to , which satisfies the follow-up constraint.
| Approach | Rating | Time Complexity | Space Complexity | Advantages | Disadvantages |
|---|---|---|---|---|---|
| Two Stacks - Eager Transfer | for pop/peek, for push | Easy to reason about; stack1 always holds the full queue state | ⚠️ Double transfer per pop/peek makes every read | ||
| Two Stacks - Lazy Transfer | worst / amortized for pop/peek, for push | Each element is transferred at most once, giving amortized per operation | empty must check both stacks; worst-case single call is still |
| Approach | push | pop | peek | empty |
|---|---|---|---|---|
| Standard Queue | ||||
| Two Stacks - Eager Transfer | ⚠️ | ⚠️ | ||
| Two Stacks - Lazy Transfer | worst / amortized ⚠️ | worst / amortized ⚠️ |
| Approach | push | pop | peek | empty | Total |
|---|---|---|---|---|---|
| Standard Queue | |||||
| Two Stacks - Eager Transfer | ℹ️ | ℹ️ | |||
| Two Stacks - Lazy Transfer | ℹ️ | ℹ️ |
The optimal approach for this problem is Solution 2: Two Stacks - Lazy Transfer, which achieves worst case / amortized time complexity and space complexity. By only transferring elements lazily when out_stack is empty, each element crosses stacks at most once, satisfying the follow-up's amortized requirement.