Implement Stack using Queues
Description
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 elementxto 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()— Returnstrueif the stack is empty,falseotherwise.
Notes:
- You must use only standard operations of a queue, which means only
push to back,peek/pop from front,size, andis emptyoperations 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.
Follow-up: Can you implement the stack using only one queue?
Examples
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); // stack is [1]
- myStack.push(2); // stack is [1, 2] (2 is on top)
- myStack.top(); // returns 2
- myStack.pop(); // returns 2, stack becomes [1]
- myStack.empty(); // returns false, stack still has element 1
Constraints
- 1 ≤ x ≤ 9
- At most 100 calls will be made to
push,pop,top, andempty - All the calls to
popandtopare valid (stack won't be empty when these are called)
Editorial
Brute Force
Intuition
A stack is LIFO (Last-In, First-Out), but a queue is FIFO (First-In, First-Out). These are opposite ordering behaviors. The challenge is to make a FIFO data structure behave like a LIFO one.
The core insight is: when we push a new element, it should become the element that comes out first (since it's the "top" of the stack). But a queue places new elements at the back, not the front. So we need to rearrange the queue so the newest element ends up at the front.
The brute force approach uses two queues: q1 (the main queue) and q2 (a helper). Every time we push, we:
- Put the new element into the empty
q2 - Move everything from
q1intoq2(one by one, from front to back) - Swap
q1andq2
After this, q1 always has the newest element at the front. So pop() and top() just interact with the front of q1 — which is O(1).
Think of it like a line of people. When a new person arrives and needs to be at the front (stack behavior), you move the new person into an empty second line, then have everyone from the first line join behind them in the second line, then relabel the second line as the main line.
Step-by-Step Explanation
Let's trace: push(1), push(2), top(), pop(), push(3), pop()
Step 1 — push(1): q2 is empty. Enqueue 1 into q2. q1 is also empty, so nothing to move. Swap: q1 = [1], q2 = [].
Step 2 — push(2): Enqueue 2 into q2: q2 = [2]. Move all of q1 into q2: dequeue 1 from q1, enqueue into q2. Now q2 = [2, 1]. Swap: q1 = [2, 1], q2 = [].
Step 3 — top(): Return front of q1 = 2. This is correct — 2 was pushed last, so it should be on top.
Step 4 — pop(): Dequeue front of q1: returns 2. q1 = [1]. Correct — 2 is removed from the top.
Step 5 — push(3): Enqueue 3 into q2: q2 = [3]. Move all of q1 into q2: dequeue 1, enqueue into q2. q2 = [3, 1]. Swap: q1 = [3, 1], q2 = [].
Step 6 — pop(): Dequeue front of q1: returns 3. q1 = [1]. Correct — 3 was the most recently pushed element.
Two Queues — Push-Costly Approach — Watch how two queues work together to simulate a stack. On each push, the new element goes into q2, all elements from q1 are moved behind it, then the queues are swapped so q1 always has the stack's top at its front.
Algorithm
Use two queues, q1 (main) and q2 (helper).
push(x):
- Enqueue
xintoq2 - While
q1is not empty: dequeue fromq1and enqueue intoq2 - Swap
q1andq2
pop():
- Dequeue and return the front element of
q1
top():
- Return the front element of
q1(without removing it)
empty():
- Return whether
q1is empty
Code
class MyStack {
private:
queue<int> q1, q2;
public:
MyStack() {}
void push(int x) {
q2.push(x);
while (!q1.empty()) {
q2.push(q1.front());
q1.pop();
}
swap(q1, q2);
}
int pop() {
int top = q1.front();
q1.pop();
return top;
}
int top() {
return q1.front();
}
bool empty() {
return q1.empty();
}
};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) == 0class MyStack {
private Queue<Integer> q1;
private Queue<Integer> q2;
public MyStack() {
q1 = new LinkedList<>();
q2 = new LinkedList<>();
}
public void push(int x) {
q2.add(x);
while (!q1.isEmpty()) {
q2.add(q1.poll());
}
Queue<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();
}
}Complexity Analysis
Time Complexity:
push(x): O(n) — We move all n existing elements fromq1toq2one at a time, then swap. Each move involves a dequeue and enqueue, both O(1), done n times.pop(): O(1) — Simply dequeue from the front ofq1.top(): O(1) — Peek at the front ofq1.empty(): O(1) — Check ifq1is empty.
Space Complexity: O(n)
We store all n elements across the two queues. At any point, q2 is empty and q1 holds all elements, so total space is O(n). The second queue q2 is only temporarily used during push.
Why This Approach Is Not Efficient
The two-queue push-costly approach works correctly, but it uses two separate queues and requires moving all elements during every push operation. While the time complexity cannot be improved beyond O(n) for at least one of push or pop (you must pay the cost of reordering somewhere), we can ask: do we really need two queues?
The answer is no. The helper queue q2 is only used temporarily during push — we enqueue the new element into it, transfer everything from q1, and then swap. But if we think carefully, we can achieve the exact same effect with a single queue: push the new element to the back, then rotate the existing elements behind it by repeatedly dequeuing from the front and enqueuing to the back.
This eliminates the second queue entirely, halving memory overhead (though the asymptotic space remains O(n)) and simplifying the code. It also directly answers the problem's follow-up question: "Can you implement the stack using only one queue?"
The key realization is that a queue's circular nature (front to back) lets us rotate elements into the correct order without needing a separate container.
Optimal Approach - Single Queue
Intuition
Instead of using two queues, we can achieve the same stack behavior with just one queue. The trick is to use the queue's own circular rotation to rearrange elements.
When we push a new element:
- Enqueue it at the back of the queue (normal queue operation)
- Then rotate all the elements that were already in the queue: dequeue from the front and re-enqueue to the back, repeating
(size - 1)times
After this rotation, the newly pushed element is at the front of the queue. Every element behind it is in reverse insertion order — exactly the LIFO order we need.
Imagine a circular conveyor belt. When a new item is placed at the end, we spin the belt until that item reaches the front. All the older items naturally line up behind it in the correct stack order.
Pop and top simply interact with the front of the queue, which is always the stack's top element. This is O(1).
Step-by-Step Explanation
Let's trace: push(1), push(2), push(3), top(), pop(), pop()
Step 1 — push(1): Enqueue 1. Queue: [1]. Size is 1, so rotate 0 times (nothing to rotate). Queue stays [1]. The front is 1, which is the stack top.
Step 2 — push(2): Enqueue 2. Queue: [1, 2]. Now rotate (size - 1) = 1 time: dequeue 1, enqueue 1 back. Queue: [2, 1]. The front is 2 (newest element), which is the stack top.
Step 3 — push(3): Enqueue 3. Queue: [2, 1, 3]. Rotate 2 times:
- Dequeue 2, enqueue 2: Queue: [1, 3, 2]
- Dequeue 1, enqueue 1: Queue: [3, 2, 1]
The front is 3 (newest), followed by 2, then 1. Perfect LIFO order!
Step 4 — top(): Peek front = 3. Correct.
Step 5 — pop(): Dequeue front = 3, return 3. Queue: [2, 1]. Correct — 3 was the most recent.
Step 6 — pop(): Dequeue front = 2, return 2. Queue: [1]. Correct — 2 was next.
Single Queue — Rotation-Based Stack Simulation — Watch how a single queue simulates a stack using rotation. After pushing a new element to the back, we rotate all previous elements behind it so the newest is always at the front.
Algorithm
Use a single queue.
push(x):
- Record the current size of the queue as
n - Enqueue
xat the back of the queue - Rotate
ntimes: dequeue from the front and enqueue back to the rear - After rotation,
xis at the front of the queue
pop():
- Dequeue and return the front element
top():
- Peek and return the front element without removing it
empty():
- Return whether the queue is empty
Code
class MyStack {
private:
queue<int> q;
public:
MyStack() {}
void push(int x) {
int n = q.size();
q.push(x);
for (int i = 0; i < n; i++) {
q.push(q.front());
q.pop();
}
}
int pop() {
int top = q.front();
q.pop();
return top;
}
int top() {
return q.front();
}
bool empty() {
return q.empty();
}
};from collections import deque
class MyStack:
def __init__(self):
self.q = deque()
def push(self, x: int) -> None:
n = len(self.q)
self.q.append(x)
for _ in range(n):
self.q.append(self.q.popleft())
def pop(self) -> int:
return self.q.popleft()
def top(self) -> int:
return self.q[0]
def empty(self) -> bool:
return len(self.q) == 0class MyStack {
private Queue<Integer> q;
public MyStack() {
q = new LinkedList<>();
}
public void push(int x) {
int n = q.size();
q.add(x);
for (int i = 0; i < n; i++) {
q.add(q.poll());
}
}
public int pop() {
return q.poll();
}
public int top() {
return q.peek();
}
public boolean empty() {
return q.isEmpty();
}
}Complexity Analysis
Time Complexity:
push(x): O(n) — We enqueue the new element (O(1)), then perform n rotations. Each rotation involves one dequeue and one enqueue, both O(1). Total: O(n).pop(): O(1) — Dequeue from the front.top(): O(1) — Peek at the front.empty(): O(1) — Check queue size.
Space Complexity: O(n)
We use a single queue storing all n elements. This is optimal — we must store n elements somewhere, so O(n) space is unavoidable.
Compared to the two-queue approach, this uses half the queue objects (1 vs 2), resulting in lower constant-factor memory overhead. The asymptotic complexities are identical, but the single-queue approach is cleaner, simpler, and answers the problem's follow-up challenge.