232. Implement Queue using Stacks - Explanation
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 elementxto 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()Returnstrueif the queue is empty,falseotherwise.
Notes:
- You must use only standard operations of a stack, which means only
pushtotop,peek/popfromtop,size, andis emptyoperations 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
100calls will be made topush,pop,peek, andempty. - All the calls to
popandpeekare 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
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
- For
push: Simply push the element ontostack1. - For
pop: Move all elements except the last fromstack1tostack2, pop the remaining element fromstack1, then move all elements back fromstack2tostack1. - For
peek: Same aspop, but instead of popping, just read the top element ofstack1after moving elements. - For
empty: Returntrueifstack1is 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.stack1Time & Space Complexity
- Time complexity:
- time for initialization.
- time for each and function calls.
- time for each and function calls.
- Space complexity:
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
- For
push: Push the element ontos1. - For
pop: Ifs2is empty, transfer all elements froms1tos2. Then pop froms2. - For
peek: Ifs2is empty, transfer all elements froms1tos2. Then return the top ofs2. - For
empty: Returntrueif boths1ands2are 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)) == 0Time & Space Complexity
- Time complexity:
- time for initialization.
- time for each and function calls.
- amortized time for each and function calls.
- Space complexity:
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.
Sign in to join the discussion