Queue transformations
Building one structure from another and reshaping a queue: a queue from two stacks, a stack from queues, rotation, reversal and the first non-repeating character in a stream.
Learning goals
- Implement a queue with two stacks and explain its amortised cost
- Rotate and reverse a queue using only queue and stack operations
- Solve the first non-repeating character problem with a queue and counts
Prerequisites
Queue using two stacks
Reversing a stack's contents once turns LIFO into FIFO. Keep an in stack that receives every enqueue and an out stack that serves every dequeue. When out is empty, pop everything from in and push it onto out: the oldest value ends up on top. Values already in out are always older than any value in in, so the order is right.
A single dequeue can move many values, but each value moves across at most once, so over a run of operations the cost is O(1) amortised per operation. The animation shows the moves, and the final message counts them.
1class QueueFromStacks {2private final Deque<Integer> in = new ArrayDeque<>(), out = new ArrayDeque<>();3void enqueue(int x) { in.push(x); }4int dequeue() {5if (out.isEmpty())6while (!in.isEmpty()) out.push(in.pop()); // reverses the order, once per batch7return out.pop();8}9}
Best
O(1)
Average
O(1) amortised
Worst
O(n) for one dequeue
Space
O(n)
Queue built from two stacks
Interactive · edit the inputin (enqueue pushes here)
out (dequeue pops here)
Two empty stacks. in receives new values; out serves dequeues. Reversing a stack's order once turns LIFO into FIFO.
1class QueueFromStacks {2private final Deque<Integer> in = new ArrayDeque<>(), out = new ArrayDeque<>();3void enqueue(int x) { in.push(x); }4int dequeue() {5if (out.isEmpty())6while (!in.isEmpty()) out.push(in.pop()); // reverses the order, once per batch7return out.pop();8}9}
Live state
in
0
out
0
moves
0
Execution trace (dry run table)Show 18 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | in=0 out=0 moves=0 | Two empty stacks. in receives new values; out serves dequeues. Reversing a stack's order once turns LIFO into FIFO. |
| 2 | L3 | op=enqueue(1) in=1 out=0 moves=0 | enqueue(1): push onto in. O(1). |
| 3 | L3 | op=enqueue(2) in=2 out=0 moves=0 | enqueue(2): push onto in. O(1). |
| 4 | L3 | op=enqueue(3) in=3 out=0 moves=0 | enqueue(3): push onto in. O(1). |
| 5 | L5 | op=dequeue() in=3 out=0 moves=0 | out is empty, so everything in in must move across. |
| 6 | L6 | op=dequeue() moved=3 in=2 out=1 moves=1 | out.push(in.pop()) moves 3. The oldest value ends up on top of out. |
| 7 | L6 | op=dequeue() moved=2 in=1 out=2 moves=2 | out.push(in.pop()) moves 2. The oldest value ends up on top of out. |
| 8 | L6 | op=dequeue() moved=1 in=0 out=3 moves=3 | out.push(in.pop()) moves 1. The oldest value ends up on top of out. |
| 9 | L7 | op=dequeue() returned=1 in=0 out=2 moves=3 | out.pop() returns 1: the value that was enqueued first. |
| 10 | L3 | op=enqueue(4) in=1 out=2 moves=3 | enqueue(4): push onto in. O(1). |
| 11 | L5 | op=dequeue() in=1 out=2 moves=3 | out is not empty, so its top is already the oldest value: no transfer needed. |
| 12 | L7 | op=dequeue() returned=2 in=1 out=1 moves=3 | out.pop() returns 2: the value that was enqueued first. |
| 13 | L5 | op=dequeue() in=1 out=1 moves=3 | out is not empty, so its top is already the oldest value: no transfer needed. |
| 14 | L7 | op=dequeue() returned=3 in=1 out=0 moves=3 | out.pop() returns 3: the value that was enqueued first. |
| 15 | L5 | op=dequeue() in=1 out=0 moves=3 | out is empty, so everything in in must move across. |
| 16 | L6 | op=dequeue() moved=4 in=0 out=1 moves=4 | out.push(in.pop()) moves 4. The oldest value ends up on top of out. |
| 17 | L7 | op=dequeue() returned=4 in=0 out=0 moves=4 | out.pop() returns 4: the value that was enqueued first. |
| 18 | L9 | in=0 out=0 moves=4 return=[] | Every value moved across at most once (4 moves in total), so dequeue is O(1) amortised. Queue order, front first: []. |
Stack using two queues
A stack needs the newest value to leave first, but a queue serves the oldest. The usual fix is to make push expensive: after adding the new value, rotate the queue by moving every older value behind it, so the new value is at the front. Then pop is a plain dequeue.
With one queue the rotation is size - 1 times offer(poll()); with two queues the values are shuffled between them. Either way push costs O(n) and pop costs O(1). The animation “Push, pop, push” uses the single-queue form.
1class StackFromQueue {2private final Queue<Integer> q = new ArrayDeque<>();3void push(int x) {4q.offer(x);5for (int i = 0; i < q.size() - 1; i++) q.offer(q.poll()); // move all older values behind x6}7int pop() { return q.remove(); } // the newest value is at the front8int top() { return q.element(); }9}
Implementing a stack with one queue
Interactive · edit the inputqueue (front = stack top)
A queue is first-in first-out, a stack is last-in first-out. Keeping the newest element at the queue's front makes pop and top trivial; push does the work.
1class StackUsingQueue {2private final Queue<Integer> q = new ArrayDeque<>(); // the front of the queue is the top of the stack3void push(int x) {4q.offer(x);5for (int i = 0; i < q.size() - 1; i++) q.offer(q.poll()); // rotate: the new element moves to the front6}7int pop() { return q.poll(); } // O(1)8int top() { return q.peek(); }9}
Live state
Execution trace (dry run table)Show 17 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | A queue is first-in first-out, a stack is last-in first-out. Keeping the newest element at the queue's front makes pop and top trivial; push does the work. | |
| 2 | L4 | op=push(1) size=1 | offer(1): it joins the back of the queue, which is the wrong end for a stack. |
| 3 | L5 | top=1 | 1 is now at the front: it is the stack's top. push cost 0 rotations: O(n). |
| 4 | L4 | op=push(2) size=2 | offer(2): it joins the back of the queue, which is the wrong end for a stack. |
| 5 | L5 | rotation=1 moves=1 | Rotate: poll the front and offer it to the back (1 of 1). |
| 6 | L5 | top=2 | 2 is now at the front: it is the stack's top. push cost 1 rotation: O(n). |
| 7 | L4 | op=push(3) size=3 | offer(3): it joins the back of the queue, which is the wrong end for a stack. |
| 8 | L5 | rotation=1 moves=2 | Rotate: poll the front and offer it to the back (1 of 2). |
| 9 | L5 | rotation=2 moves=3 | Rotate: poll the front and offer it to the back (2 of 2). |
| 10 | L5 | top=3 | 3 is now at the front: it is the stack's top. push cost 2 rotations: O(n). |
| 11 | L7 | op=pop() returned=3 | poll() returns 3, the most recently pushed value still in the stack: O(1). |
| 12 | L4 | op=push(4) size=3 | offer(4): it joins the back of the queue, which is the wrong end for a stack. |
| 13 | L5 | rotation=1 moves=4 | Rotate: poll the front and offer it to the back (1 of 2). |
| 14 | L5 | rotation=2 moves=5 | Rotate: poll the front and offer it to the back (2 of 2). |
| 15 | L5 | top=4 | 4 is now at the front: it is the stack's top. push cost 2 rotations: O(n). |
| 16 | L7 | op=pop() returned=4 | poll() returns 4, the most recently pushed value still in the stack: O(1). |
| 17 | L9 | moves=5 return=[2, 1] | Stack (top first): [2, 1]; 5 queue rotations in total. Either push or pop must cost O(n) when only queues are available. |
Rotate queue
Rotating a queue by k moves the front value to the rear k times: q.offer(q.poll()). Rotating by the queue size changes nothing, so only k % size rotations are needed. A rotation by k leaves the values in the same cyclic order, shifted k places.
The cost is k % size poll/offer pairs, at most size - 1. Try the “k > size” preset to see that 6 rotations of a 4-element queue equal 2.
1static void rotate(Queue<Integer> q, int k) {2int times = k % q.size(); // a full turn changes nothing3for (int i = 0; i < times; i++)4q.offer(q.poll()); // front goes to the rear5}
Rotate a queue by k positions
Interactive · edit the inputqueue · front → rear
k = 2, size = 6: only 2 rotations are needed (k % size).
1static void rotate(Queue<Integer> q, int k) {2int times = k % q.size(); // a full turn changes nothing3for (int i = 0; i < times; i++)4q.offer(q.poll()); // front goes to the rear5}
Live state
k
2
size
6
times
2
Execution trace (dry run table)Show 4 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | k=2 size=6 times=2 | k = 2, size = 6: only 2 rotations are needed (k % size). |
| 2 | L4 | i=0 moved=1 times=2 | poll() takes 1 from the front and offer() puts it at the rear. |
| 3 | L4 | i=1 moved=2 times=2 | poll() takes 2 from the front and offer() puts it at the rear. |
| 4 | L5 | return=[3, 4, 5, 6, 1, 2] | Rotated queue: [3, 4, 5, 6, 1, 2]. It cost 2 poll/offer pairs. |
Reverse queue
A queue cannot be reversed in place using only enqueue and dequeue, but a stack can do it: move every value from the queue onto a stack (the front ends up deepest), then move them back (the old rear is now popped first and becomes the new front). It is O(n) time and O(n) extra space.
A recursive version does the same with the call stack: dequeue the front, reverse the rest, enqueue the saved value at the end.
1static void reverse(Queue<Integer> q) {2Deque<Integer> stack = new ArrayDeque<>();3while (!q.isEmpty()) stack.push(q.poll()); // front first in, so the front ends deepest4while (!stack.isEmpty()) q.offer(stack.pop()); // top first out: the old rear is now the front5}
Reverse a queue with a stack
Interactive · edit the inputqueue · front → rear
stack
A queue gives values out oldest first, a stack newest first. Passing everything through a stack flips the order.
1static void reverse(Queue<Integer> q) {2Deque<Integer> stack = new ArrayDeque<>();3while (!q.isEmpty()) stack.push(q.poll()); // front first in, so front ends deepest4while (!stack.isEmpty()) q.offer(stack.pop()); // top first out: the old rear is now the front5}
Live state
queue
5
stack
0
Execution trace (dry run table)Show 12 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | queue=5 stack=0 | A queue gives values out oldest first, a stack newest first. Passing everything through a stack flips the order. |
| 2 | L3 | moved=1 queue=4 stack=1 | poll() 1 from the queue and push it on the stack. |
| 3 | L3 | moved=2 queue=3 stack=2 | poll() 2 from the queue and push it on the stack. |
| 4 | L3 | moved=3 queue=2 stack=3 | poll() 3 from the queue and push it on the stack. |
| 5 | L3 | moved=4 queue=1 stack=4 | poll() 4 from the queue and push it on the stack. |
| 6 | L3 | moved=5 queue=0 stack=5 | poll() 5 from the queue and push it on the stack. |
| 7 | L4 | moved=5 queue=1 stack=4 | pop() 5 from the stack and offer() it to the queue. |
| 8 | L4 | moved=4 queue=2 stack=3 | pop() 4 from the stack and offer() it to the queue. |
| 9 | L4 | moved=3 queue=3 stack=2 | pop() 3 from the stack and offer() it to the queue. |
| 10 | L4 | moved=2 queue=4 stack=1 | pop() 2 from the stack and offer() it to the queue. |
| 11 | L4 | moved=1 queue=5 stack=0 | pop() 1 from the stack and offer() it to the queue. |
| 12 | L5 | return=[5, 4, 3, 2, 1] | Reversed queue: [5, 4, 3, 2, 1]. O(n) time, O(n) extra space for the stack. |
First non-repeating stream
Given a stream of characters, report after each character the first one that has appeared exactly once so far. Keep a count per character and a queue of candidates in arrival order. A new character joins the queue; before answering, remove characters from the *front* while their count is above 1. The front is then the answer, or # if the queue is empty.
The removal is lazy: a repeated character stays in the queue until it reaches the front. Each character enters once and leaves at most once, so the whole stream is O(n). Try “Late survivor” (abbacd): the answer changes from a to # and later to c.
1static String firstNonRepeating(String s) {2int[] count = new int[26];3Deque<Character> q = new ArrayDeque<>();4StringBuilder out = new StringBuilder();5for (char c : s.toCharArray()) {6if (++count[c - 'a'] == 1) q.addLast(c); // first sighting: a candidate7while (!q.isEmpty() && count[q.peekFirst() - 'a'] > 1) q.pollFirst(); // drop repeats at the front8out.append(q.isEmpty() ? '#' : q.peekFirst());9}10return out.toString();11}
Best
O(n)
Average
O(n)
Worst
O(n)
Space
O(1) for counts, O(k) for the queue
First non-repeating character in a stream
Interactive · edit the inputstream so far
queue of candidates
counts
answer so far
After every character we must report the first character that has appeared exactly once so far. The queue keeps candidates in arrival order.
1static String firstNonRepeating(String s) {2int[] count = new int[26];3Deque<Character> q = new ArrayDeque<>();4StringBuilder out = new StringBuilder();5for (char c : s.toCharArray()) {6if (++count[c - 'a'] == 1) q.addLast(c); // first sighting: a candidate7while (!q.isEmpty() && count[q.peekFirst() - 'a'] > 1) q.pollFirst(); // drop repeats at the front8out.append(q.isEmpty() ? '#' : q.peekFirst());9}10return out.toString();11}
Live state
length
7
Execution trace (dry run table)Show 19 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | length=7 | After every character we must report the first character that has appeared exactly once so far. The queue keeps candidates in arrival order. |
| 2 | L6 | c=a count=1 | 'a' is new: count 1, and it joins the back of the candidate queue. |
| 3 | L8 | answer=a | The first non-repeating character is 'a'. |
| 4 | L6 | c=a count=2 | 'a' has now appeared 2 times: it is no longer a candidate, but it may still sit in the queue. |
| 5 | L7 | dropped=a | The front 'a' repeats, so it is removed. Dropping is lazy: only the front is ever checked. |
| 6 | L8 | answer=# | No character has appeared exactly once: report '#'. |
| 7 | L6 | c=b count=1 | 'b' is new: count 1, and it joins the back of the candidate queue. |
| 8 | L8 | answer=b | The first non-repeating character is 'b'. |
| 9 | L6 | c=c count=1 | 'c' is new: count 1, and it joins the back of the candidate queue. |
| 10 | L8 | answer=b | The first non-repeating character is 'b'. |
| 11 | L6 | c=b count=2 | 'b' has now appeared 2 times: it is no longer a candidate, but it may still sit in the queue. |
| 12 | L7 | dropped=b | The front 'b' repeats, so it is removed. Dropping is lazy: only the front is ever checked. |
| 13 | L8 | answer=c | The first non-repeating character is 'c'. |
| 14 | L6 | c=d count=1 | 'd' is new: count 1, and it joins the back of the candidate queue. |
| 15 | L8 | answer=c | The first non-repeating character is 'c'. |
| 16 | L6 | c=c count=2 | 'c' has now appeared 2 times: it is no longer a candidate, but it may still sit in the queue. |
| 17 | L7 | dropped=c | The front 'c' repeats, so it is removed. Dropping is lazy: only the front is ever checked. |
| 18 | L8 | answer=d | The first non-repeating character is 'd'. |
| 19 | L10 | return="a#bbccd" | Answer string: "a#bbccd". Each character is added and removed at most once, so the whole stream costs O(n). |
Practice ladder
Now try it yourself
Easy
- Implement Queue using Stacks
- Implement Stack using Queues
Medium
- Design Circular Queue
- First Unique Character in a String
Advanced
- Sliding Window Maximum
- Design Hit Counter
Key takeaways
- Two stacks make a queue: enqueue on in, dequeue from out, refill out only when empty (O(1) amortised).
- A stack from queues costs O(n) for either push or pop.
- A queue of candidates plus counts answers first-non-repeating-in-a-stream in O(n).