Topics › Stacks and Queues › Learning
Implement Stack using Queue
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
Implement a last-in-first-out stack (push, pop, top, isEmpty, size) using only the standard operations of a queue: add to the rear (offer), remove from the front (poll), read the front (peek), size and isEmpty. You may use one or two queues, but you may not peek at the rear, iterate, or index into them.
Constraints: up to 10^5 operations, values fit in int. pop and top on an empty stack are errors; the code throws NoSuchElementException.
Example 1 (LeetCode 225)
push(1), push(2), top(), pop(), isEmpty()
output: 2, 2, false // 2 was pushed last, so it is on top
Example 2
push(1), push(2), push(3), top(), top(), pop(), pop(), size()
output: 3, 3, 3, 2, 1 // top() must not disturb the stack
The three implementations share this interface so one test script can drive all of them:
interface IntStack {
void push(int x);
int pop();
int top();
boolean isEmpty();
int size();
}
Approach 1: Two queues, cheap push, costly pop
Intuition. A queue hands back elements oldest first; a stack wants newest first. If the elements are stored in insertion order, the stack top is the element at the rear of the queue, which a queue cannot reach directly. We can reach it indirectly: move every element except the last one into a spare queue, in order, and what remains at the front is the newest element. After taking it, the spare queue holds the rest in the original order, so just swap the two references and the spare is empty again.
Algorithm.
push(x):q1.offer(x).pop(): whileq1has more than one element,q2.offer(q1.poll()). Take the single remaining element ofq1as the answer. Swapq1andq2.top(): performpop()and thenofferthe popped value back intoq1. It becomes the rear again, which is where the top lives.isEmpty()andsize()delegate toq1.
static class TwoQueuesCostlyPop implements IntStack {
private Queue<Integer> q1 = new ArrayDeque<>(); // holds the stack, oldest at the front
private Queue<Integer> q2 = new ArrayDeque<>(); // always empty between operations
public void push(int x) {
q1.offer(x);
}
public int pop() {
if (q1.isEmpty()) throw new NoSuchElementException("stack underflow");
while (q1.size() > 1) q2.offer(q1.poll()); // everything except the most recent element
int x = q1.poll(); // the most recent element is the stack top
Queue<Integer> t = q1; // swap roles; q2 is empty again
q1 = q2;
q2 = t;
return x;
}
public int top() {
int x = pop();
q1.offer(x); // re-append: it is the newest element again
return x;
}
public boolean isEmpty() {
return q1.isEmpty();
}
public int size() {
return q1.size();
}
}
Complexity. Time O(1) for push, O(n) for pop and top (each moves n - 1 elements). Space O(n) across the two queues; at most one of them is non-empty between operations.
Why the swap is safe: after the loop q2 holds n - 1 elements in the same relative order they had in q1, which is exactly the stack with its top removed. Renaming the queues costs nothing and avoids copying everything back.
Approach 2: Two queues, costly push, cheap pop
Intuition. Flip the trade. Instead of hunting for the newest element at pop time, arrange on every push that the newest element is at the front. Put x into the empty spare queue first, then append all existing elements behind it; the spare now contains the stack in newest-first order, so swap. pop and top are then plain poll and peek.
Algorithm.
push(x):q2.offer(x); whileq1is non-empty,q2.offer(q1.poll()); swapq1andq2.pop():q1.poll().top():q1.peek().isEmpty()andsize()delegate toq1.
static class TwoQueuesCostlyPush implements IntStack {
private Queue<Integer> q1 = new ArrayDeque<>(); // holds the stack, newest at the front
private Queue<Integer> q2 = new ArrayDeque<>(); // always empty between operations
public void push(int x) {
q2.offer(x); // x first, so it ends up at the front
while (!q1.isEmpty()) q2.offer(q1.poll()); // older elements behind it, order kept
Queue<Integer> t = q1;
q1 = q2;
q2 = t;
}
public int pop() {
if (q1.isEmpty()) throw new NoSuchElementException("stack underflow");
return q1.poll();
}
public int top() {
if (q1.isEmpty()) throw new NoSuchElementException("stack is empty");
return q1.peek();
}
public boolean isEmpty() {
return q1.isEmpty();
}
public int size() {
return q1.size();
}
}
Complexity. Time O(n) for push, O(1) for everything else. Space O(n).
Choose between approaches 1 and 2 by workload: if reads (top) dominate, pay on push; if writes dominate, pay on pop. In an interview, be ready to state that trade-off.
Approach 3: One queue with rotation
Intuition. The second queue in approach 2 exists only to hold the old elements while x is placed in front of them. A single queue can do that itself: after offer(x), the queue is [old_1, ..., old_{n-1}, x]. Polling the front and offering it back to the rear once moves old_1 behind x. Doing that n - 1 times moves all the old elements behind x, leaving [x, old_1, ..., old_{n-1}]. The front of the queue is now the top of the stack and the rest of the queue is the rest of the stack in order, so the invariant “queue order equals stack order, top first” is restored.
Algorithm.
push(x):q.offer(x); then repeatq.offer(q.poll())exactlysize - 1times.pop():q.poll().top():q.peek().isEmpty()andsize()delegate toq.
static class OneQueue implements IntStack {
private final Queue<Integer> q = new ArrayDeque<>(); // newest at the front
public void push(int x) {
q.offer(x);
for (int i = 1; i < q.size(); i++) q.offer(q.poll()); // n-1 rotations move x to the front
}
public int pop() {
if (q.isEmpty()) throw new NoSuchElementException("stack underflow");
return q.poll();
}
public int top() {
if (q.isEmpty()) throw new NoSuchElementException("stack is empty");
return q.peek();
}
public boolean isEmpty() {
return q.isEmpty();
}
public int size() {
return q.size();
}
}
Dry run of push(1), push(2), push(3) followed by pop():
| operation | after offer | rotations | queue (front → rear) |
|---|---|---|---|
| push(1) | [1] | 0 | [1] |
| push(2) | [1, 2] | 1: move 1 behind 2 | [2, 1] |
| push(3) | [2, 1, 3] | 2: move 2, then 1 | [3, 2, 1] |
| pop() | returns 3, queue [2, 1] |
Complexity. Time O(n) for push, O(1) for pop, top, isEmpty, size. Space O(n) in a single queue, the smallest footprint of the three.
The loop bound i < q.size() is evaluated each iteration, but size() does not change during a rotation (one poll, one offer), so the loop runs exactly n - 1 times.
Edge cases and pitfalls
- Empty stack.
popandtopmust check for emptiness before polling;Queue.poll()returnsnullrather than throwing, and unboxing thatnullinto anintgives a confusingNullPointerException. The code checks explicitly and throwsNoSuchElementException. topin approach 1 must restore the stack. Popping and forgetting to re-offer the element loses it. Re-offering puts it at the rear, which is correct because the rear is the top in that layout.- Which queue is “main”. After a swap the variable names refer to different objects. Keep the invariant “
q2is empty between operations” and the code stays simple; the test script checkssize()after every operation to catch a forgotten swap. - Off-by-one in the rotation. Rotating
ntimes instead ofn - 1returns the queue to its pre-rotation order, withxat the rear again. Rotatingn - 2times leaves one old element in front ofx. - Using forbidden operations.
ArrayDequealso offerspeekLastandpollLast, which would make the problem trivial but violate its spirit. The code declares the fields asQueue<Integer>, which exposes only the allowed methods.
Related problems
- Implement Queue using Stack is the mirror image, where two stacks give amortised
O(1)queue operations. - Implement Stack using Queue (using single queue) is the SDE-sheet name for approach 3.
- Implement Stack using Arrays is the direct implementation that these approaches are compared against.
- Implement Queue using Arrays shows what the underlying queue looks like inside.