Editorial
Core insight
Implement Stack using Queues · Design Patterns
Core Insight for Implement Stack using Queues
The fundamental difference between a Stack and a Queue is where elements are removed.
- Queue (FIFO): Removes from the front.
- Stack (LIFO): Removes from the top (which corresponds to the back of the queue, i.e., the most recently added element).
To implement a Stack using a Queue, we need the "back" element to be accessible at the "front."
The core insight is Queue Rotation. When we push a new element into a queue of size , is placed at the back (position ). If we immediately remove the first elements from the front and add them back to the queue, the order cyclically shifts. The element (originally at the back) will "rotate" all the way to the front.
Visual Description:
Imagine a queue containing elements [1, 2].
- We push
3. The queue becomes[1, 2, 3]. - To make
3the top, we rotate. - Pop
1and push it to back:[2, 3, 1]. - Pop
2and push it to back:[3, 1, 2]. - Now
3is at the front. A standard queuepopoperation will correctly return3, satisfying the Stack LIFO requirement.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 225: Implement Stack using Queues Solution & Explanation
Problem Overview
TL;DR: The optimal solution uses a single queue and rotates the elements after every insertion so that the most recently added element always sits at the front, allowing access for stack operations.
The LeetCode 225 problem, "Implement Stack using Queues," challenges us to construct a Last-In-First-Out (LIFO) stack data structure using only First-In-First-Out (FIFO) queues. We must support the standard stack interface: push, pop, top, and empty, while strictly adhering to standard queue operations (enqueue, dequeue, size, and peek). This is a popular interview question used to test object-oriented design skills and data structure manipulation.
Brute Force Approach for Implement Stack using Queues
The most intuitive or "brute force" way to solve this is by using two separate queues, often referred to as q1 (source) and q2 (helper).
In this approach, to maintain the LIFO property, we ensure that the element to be popped is always at the front of q1.
- Push Operation: When a new element arrives, we enqueue it into the empty
q2. Then, we dequeue every element fromq1and enqueue them intoq2. Finally, we swap the references ofq1andq2. - Pop Operation: Simply dequeue from
q1.
Why this is suboptimal: While correct, this approach requires maintaining two distinct data structures. Although the time complexity is the same as the optimal approach ( for push, for pop), the management of two queues adds unnecessary overhead and code complexity. Furthermore, the problem statement includes a follow-up asking for a solution using only one queue, which the brute force method fails to address.
Algorithm Strategy: Design Patterns
We will implement the MyStack class using a single queue.
- State: Maintain a single queue data structure.
- Push Strategy:
- Add the new element to the queue.
- Immediately rotate the queue. If the queue size is after insertion, we perform dequeue-then-enqueue operations.
- This invariant ensures the newest element is always at the head of the queue.
- Pop Strategy:
- Since the "stack top" is always maintained at the "queue front," we simply perform a standard queue dequeue operation.
- Top Strategy:
- Return the front element of the queue.
- Empty Strategy:
- Check if the underlying queue is empty.
Execution Flow
Let's trace the operations: push(1), push(2), pop().
- Initialize:
queue = [] push(1):- Enqueue
1.queue = [1]. - Size is 1. Rotate
1-1 = 0times. - State:
[1](Top is 1).
- Enqueue
push(2):- Enqueue
2.queue = [1, 2]. - Size is 2. Rotate
2-1 = 1time. - Dequeue
1, Enqueue1. - State:
[2, 1](Top is 2).
- Enqueue
pop():- Dequeue front. Returns
2. - State:
[1](Top is 1).
- Dequeue front. Returns
Proof of Correctness
The correctness relies on the invariant: The queue is always sorted in reverse chronological order of insertion.
- Base Case: An empty queue represents an empty stack.
- Inductive Step: Assume the queue contains elements ordered
[Newest, ..., Oldest]. When we add a new element , it enters at the back:[Newest, ..., Oldest, E]. By rotating the previous elements behind , the new order becomes[E, Newest, ..., Oldest]. - Conclusion: Since the newest element is always at the front,
pop()(dequeue front) correctly removes the LIFO element, andtop()(peek front) correctly views it.
Pattern Reuse Notes
The logic of wrapping or adapting one data structure to serve another purpose is found in several LeetCode problems:
- LeetCode 146: LRU Cache - Combines a Hash Map and Doubly Linked List to enforce eviction policies.
- LeetCode 155: Min Stack - Adapts a stack to track the minimum element in constant time.
- LeetCode 232: Implement Queue using Stacks - The inverse of this problem; uses two stacks to simulate FIFO behavior.
- LeetCode 251: Flatten 2D Vector - Adapts a 2D array structure to behave like a linear iterator.
Understanding how to manipulate data structure invariants is key to solving these design problems efficiently.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 225
1#include <queue>
2
3class MyStack {
4private:
5 std::queue<int> q;
6
7public:
8 MyStack() {
9 // Constructor does not need to do anything specifically
10 }
11
12 void push(int x) {
13 q.push(x);
14 // Rotate the queue so the new element (at back) moves to front
15 int sz = q.size();
16 for (int i = 0; i < sz - 1; ++i) {
17 q.push(q.front());
18 q.pop();
19 }
20 }
21
22 int pop() {
23 int topElement = q.front();
24 q.pop();
25 return topElement;
26 }
27
28 int top() {
29 return q.front();
30 }
31
32 bool empty() {
33 return q.empty();
34 }
35};Java Solution for LeetCode 225
1import java.util.LinkedList;
2import java.util.Queue;
3
4class MyStack {
5 private Queue<Integer> queue;
6
7 public MyStack() {
8 // LinkedList implements Queue interface in Java
9 queue = new LinkedList<>();
10 }
11
12 public void push(int x) {
13 queue.add(x);
14 // Rotate size-1 elements to the back
15 int sz = queue.size();
16 for (int i = 0; i < sz - 1; i++) {
17 queue.add(queue.remove());
18 }
19 }
20
21 public int pop() {
22 // The front of the queue is the top of the stack
23 return queue.remove();
24 }
25
26 public int top() {
27 return queue.peek();
28 }
29
30 public boolean empty() {
31 return queue.isEmpty();
32 }
33}Python Solution for LeetCode 225
1from collections import deque
2
3class MyStack:
4
5 def __init__(self):
6 # deque serves as our queue implementation
7 self.q = deque()
8
9 def push(self, x: int) -> None:
10 self.q.append(x)
11 # Rotate the queue: pop from left and append to right
12 # We do this size-1 times to bring the new element to the front
13 for _ in range(len(self.q) - 1):
14 self.q.append(self.q.popleft())
15
16 def pop(self) -> int:
17 # Front of queue (left) is the top of stack
18 return self.q.popleft()
19
20 def top(self) -> int:
21 return self.q[0]
22
23 def empty(self) -> bool:
24 return len(self.q) == 0