DSA sheet · Stack · Stack-based design pattern
Implement Stack using Queues
This is the mirror of the previous problem. Last time we built a queue out of stacks; now we build a stack (last in, first out) out of queues (first in, first out). The teacher shows two designs with two queues: one where push is costly and pop/top are cheap, and one where push is cheap and pop/top are costly. Both are correct. Which one to pick depends on which operation you need to be fast.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run (operation-sequence table) → ⑨ complexity & remember
- Part 0 · Stacks, queues and cost from scratch
- Part A · Approach 1: costly push, O(1) pop and top
- Part B · Approach 2: O(1) push, costly pop and top
- Part C · Revision page
Part 0 · Before starting
What is a stack?
A stack is a pile of plates: you can only add or remove at the top. The last item in is the first out, called LIFO (Last In, First Out). Its operations: push(x) puts x on top, pop() removes and returns the top, top() (also called peek) reads the top without removing it, empty() says whether it has nothing.
st = [] st.append(1); st.append(2) # push 1, push 2 -> [1, 2], top = 2 st[-1] # top -> 2 st.pop() # pop -> 2 if st: print(st[-1]) # always check "not empty" before pop / top
What is a queue?
A queue is a line of people: you join at the back and leave from the front. The first item in is the first out, called FIFO (First In, First Out). In Java the teacher calls joining offer and leaving poll.
from collections import deque q = deque() q.append(1); q.append(2) # offer 1, offer 2 (join at the back) -> front [1, 2] back q[0] # peek at the front -> 1 q.popleft() # poll the front -> 1 len(q), not q # size, is it empty?
A deque could also remove from the right end, but that would make it a stack and defeat the purpose. So in this problem we only use append, popleft, [0] and len. Always check the queue isn't empty before popleft or [0].
In these notes we draw a queue as front on the left, back on the right.
The key fact that makes this problem different from the last one
| moving every item from one stack to another | moving every item from one queue to another | |
|---|---|---|
| what happens to the order | reversed (that's how we built a queue from stacks) | unchanged: the front leaves first and joins the new line first |
So simply pouring one queue into another does nothing useful. We need a smarter trick.
Cost words: O(1), O(n) and "amortised"
O(1) means a fixed small amount of work. O(n) means work that grows with the number of items. Amortised O(1) means a single call may be slow, but over a long run of calls the average is constant, because the slow calls are rare (like Problem 18, where each item was poured only once). We'll see below that this problem has no such trick: the slow operation is slow every time.
Part A · Approach 1: costly push, O(1) pop and top
LeetCode 225
1The question in simple words
Build a class MyStack with push(x), pop(), top() and empty() that behaves exactly like a stack, but inside you may only keep queues and use queue operations (add at back, remove/peek at front, size, is-empty).
The teacher's example: push 1, push 2, push 3, then pop → must give 3 (the newest), then push 4, then pop → must give 4.
2What the constraints tell us
1 ≤ x ≤ 9, at most 100 calls → O(n) per call is completely fine.- Every
popandtopis called on a non-empty stack → no "empty" error handling needed. - LeetCode's follow-up asks: can you do it with one queue? (Answered in a doubt at the end of Part A.)
3Intuition: make the newest item sit at the FRONT
Put 1, 2, 3 into a queue: front [1, 2, 3] back. A queue hands out 1 first, but a stack must hand out 3. The newest is stuck at the back.
The teacher first tries the stack-style trick: pour q1 into a second queue q2. Poll 1 → it joins q2 at the back. Poll 2 → behind 1. Poll 3 → behind 2. q2 is [1, 2, 3], the same order. Polling q2 still gives 1. Pouring alone doesn't reverse a queue.
The real idea: when the new item arrives, let it enter an EMPTY queue first. In an empty queue, the newcomer is automatically at the front. Then let all the older items line up behind it. Now the newest is at the front and the rest are behind it in newest-to-oldest order. That is exactly a stack, read from the front.
4Building the logic from the example
push 1
Put 1 into the empty helper q2: q2 = [1]. q1 has nothing to move. Now make q2 the main queue (swap names): q1 = [1], q2 = [].
push 2
Put 2 into the empty q2: q2 = [2]. Now poll everything from q1 and add it to q2: 1 joins behind 2 → q2 = [2, 1]. Swap: q1 = [2, 1], q2 = []. The front of q1 is 2, the newest ✓.
push 3
q2 = [3], then 2 and 1 join behind (in their order) → [3, 2, 1]. Swap → q1 = [3, 2, 1].
pop → 3
The newest is at the front of q1, so just q1.popleft() → 3 ✓. top is q1[0].
→ q1 already has older items, so the newcomer would land at its back, which is the wrong end. q2 is kept empty between calls on purpose, so whatever enters it first is at the front.
→ The teacher writes "q1 = q2, then q2 = a new empty queue". We don't actually copy item by item; we just swap which object is called q1. In Python:
self.q1, self.q2 = self.q2, self.q1. After the move loop, the old q1 is empty, so after the swap q2 is empty again, ready for the next push.empty() check only q1?→ q2 is only used inside push and is empty again when push finishes. Between calls, every item lives in q1.
5Approach steps
- Keep q1 (main: front = top of stack) and q2 (helper, empty between calls).
- push(x): add x to q2 → move every item from q1 to q2 → swap q1 and q2.
- pop():
q1.popleft(). - top():
q1[0]. - empty():
not q1.
6Code (Python)
from collections import deque
class MyStack:
def __init__(self):
self.q1 = deque() # main queue: FRONT = top of the stack
self.q2 = deque() # helper, always empty between calls
def push(self, x):
self.q2.append(x) # newcomer enters an empty queue: it's at the front
while self.q1: # older items line up behind it
self.q2.append(self.q1.popleft())
self.q1, self.q2 = self.q2, self.q1 # q2 becomes the main queue; old q1 (now empty) is the helper
def pop(self):
return self.q1.popleft()
def top(self):
return self.q1[0]
def empty(self):
return not self.q17Code line by line
| line | what it means |
|---|---|
| self.q2.append(x) | q2 is empty, so x is now the front of q2. The newest item is in the "first out" spot. |
| while self.q1: self.q2.append(self.q1.popleft()) | Take q1's items from its front, one by one, and line them up behind x. q1 was already newest-first, and a queue-to-queue move keeps order, so q2 becomes x, then newest → oldest. |
| self.q1, self.q2 = self.q2, self.q1 | Rename: the full queue becomes q1, the now-empty old q1 becomes the helper q2. Same as the teacher's "q1 = q2; q2 = new queue". |
| return self.q1.popleft() | The front of q1 is the newest item, which is what a stack pops. |
| return self.q1[0] | Read the front without removing it. |
| return not self.q1 | All items live in q1 between calls. |
8Dry run: operation-sequence table
The teacher's sequence: push 1, push 2, push 3, pop, push 4, pop, plus top and empty checks. Queues are drawn front on the left. "ops" counts single enqueue/dequeue steps.
| # | operation | what happens inside | q1 after | q2 after | returned | ops |
|---|---|---|---|---|---|---|
| 1 | push 1 | q2 = [1]; q1 empty; swap | [1] | [] | – | 1 |
| 2 | push 2 | q2 = [2]; 1 joins → [2, 1]; swap | [2, 1] | [] | – | 3 |
| 3 | push 3 | q2 = [3]; 2, 1 join → [3, 2, 1]; swap | [3, 2, 1] | [] | – | 5 |
| 4 | pop | poll front of q1 | [2, 1] | [] | 3 | 1 |
| 5 | push 4 | q2 = [4]; 2, 1 join → [4, 2, 1]; swap | [4, 2, 1] | [] | – | 5 |
| 6 | top | read front | [4, 2, 1] | [] | 4 | 0 |
| 7 | pop | poll front | [2, 1] | [] | 4 | 1 |
| 8 | empty | q1 has items | [2, 1] | [] | False | 0 |
Inside one push, moment by moment (pushing 3 onto [2, 1]):
(Step 3 of the table. Yellow = the top of the stack.)
9Complexity & remember
- push: O(n): all n items already in q1 are moved once (n polls + n appends) plus 1.
- pop, top, empty: O(1).
- Space: O(n).
→ Yes. Append x at the back, then rotate the older items: poll from the front and append at the back, (size − 1) times. After that, x is at the front. Same costs as Approach 1 (push O(n), pop O(1)).
from collections import deque
class MyStackOneQueue:
def __init__(self):
self.q = deque()
def push(self, x):
self.q.append(x)
for _ in range(len(self.q) - 1): # send every older item to the back
self.q.append(self.q.popleft())
def pop(self):
return self.q.popleft()
def top(self):
return self.q[0]
def empty(self):
return not self.qPart B · Approach 2: O(1) push, costly pop and top
1The question again, with the new goal
Same four operations. This time the teacher wants push in O(1). She says she likes this version more because it is easier to picture.
2What the constraints tell us now
- Still at most 100 calls, so both approaches pass. The choice is about which operation should be cheap.
- pop and top are only called when the stack has at least one item, so q1 is never empty when we start moving.
3Intuition: leave the items in arrival order, and dig out the LAST one when asked
Just append every push to q1. Then q1 is in arrival order: front [1, 2, 3] back. The top of the stack is the item at the back (3). A queue can't reach its back directly, but we can move every item EXCEPT the last one into q2. Then the only item left in q1 is the newest one. Take it (pop) or read it (top), then make q2 the main queue again.
4Building the logic from the example
push 1, push 2, push 3
Just append: q1 = [1, 2, 3]. One step each.
pop → we want 3
- While q1 has more than one item, poll from q1 and append to q2: 1 moves, then 2 moves. q1 = [3], q2 = [1, 2].
- The last item in q1 is the newest: poll it → 3.
- Swap: q1 = [1, 2], q2 = [] (the teacher writes "q1 = q2; q2 = new queue").
len(q1) > 1 and not while q1 is non-empty?→ If we moved everything, the newest would also go to q2, at its back, and we'd be stuck again. Stopping at size 1 leaves exactly the item we want alone in q1.
top → we want to READ 3 without removing it
Same moving loop, so 3 is alone in q1. Take it out and remember it in val. Then put it into q2 too (at the back, after 1 and 2), and swap. q1 = [1, 2, 3] again. Return val.
→ top must not remove anything. After the swap, q2 becomes the main queue, so if 3 isn't in q2 it is lost forever. The teacher adds one extra line for exactly this: "offer the last value into q2". It goes at the back, which is where the newest belongs in this design.
empty
After every pop/top, q2 is empty again and all items are in q1. So check only q1.
5Approach steps
- push(x):
q1.append(x). - pop(): move items from q1 to q2 while q1 has more than 1 → poll the last one → swap → return it.
- top(): same move → poll the last one → append it to q2 → swap → return it.
- empty():
not q1.
6Code (Python)
from collections import deque
class MyStack:
def __init__(self):
self.q1 = deque() # items in arrival order: BACK = top of the stack
self.q2 = deque() # helper, empty between calls
def push(self, x):
self.q1.append(x) # O(1)
def pop(self):
while len(self.q1) > 1: # move all but the newest
self.q2.append(self.q1.popleft())
val = self.q1.popleft() # the newest, now alone
self.q1, self.q2 = self.q2, self.q1 # helper becomes main
return val
def top(self):
while len(self.q1) > 1:
self.q2.append(self.q1.popleft())
val = self.q1.popleft()
self.q2.append(val) # put it back: top must not remove
self.q1, self.q2 = self.q2, self.q1
return val
def empty(self):
return not self.q17Code line by line
| line | what it means |
|---|---|
| self.q1.append(x) | Push simply joins the line. q1 stays in arrival order. |
| while len(self.q1) > 1: self.q2.append(self.q1.popleft()) | Move all older items to q2, keeping their order. Stop when only the newest is left. |
| val = self.q1.popleft() | Take out the newest item. q1 is now empty. |
| self.q2.append(val) | (top only) Return the value to the line so it isn't lost. |
| self.q1, self.q2 = self.q2, self.q1 | The queue holding the items becomes q1; the empty one becomes the helper. |
| return not self.q1 | Every item is in q1 between calls. |
8Dry run: operation-sequence table
| # | operation | what happens inside | q1 after | q2 after | returned | ops |
|---|---|---|---|---|---|---|
| 1 | push 1 | append | [1] | [] | – | 1 |
| 2 | push 2 | append | [1, 2] | [] | – | 1 |
| 3 | push 3 | append | [1, 2, 3] | [] | – | 1 |
| 4 | pop | 1, 2 → q2; poll 3; swap | [1, 2] | [] | 3 | 5 |
| 5 | push 4 | append | [1, 2, 4] | [] | – | 1 |
| 6 | top | 1, 2 → q2; poll 4; 4 → q2; swap | [1, 2, 4] | [] | 4 | 6 |
| 7 | pop | 1, 2 → q2; poll 4; swap | [1, 2] | [] | 4 | 5 |
| 8 | empty | q1 has items | [1, 2] | [] | False | 0 |
Same answers as Part A (3, 4, 4, False), just with the work done at a different time.
9Complexity & remember (with a counted example)
- push: O(1).
- pop, top: O(n): n − 1 items move every time.
- empty: O(1). Space: O(n).
Is this "amortised O(1)" like the queue-from-stacks problem? No.
In Problem 18, once items were poured into the out-stack they stayed there in the right order, so the next pops were free. Here, after a pop, q1 is back in arrival order with the newest at the back again, so the very next pop has to move n − 2 items again. The slow step repeats on every call. Let's count: push 1 … 100, then pop 100 times.
| Approach 1 (costly push) | Approach 2 (costly pop) | |
|---|---|---|
| 100 pushes | k-th push: 1 + 2(k − 1) ops → 100 + 2 × 4950 = 10,000 | 1 each = 100 |
| 100 pops | 1 each = 100 | pop with m items: 2(m − 1) + 1 = 2m − 1 ops → sum for m = 1…100 = 10,000 |
| total | 10,100 | 10,100 |
Both total about n² here, so neither is amortised O(1). The difference is where the cost lands: push-heavy work with few pops favours Approach 2; pop/top-heavy work favours Approach 1.
len(q1) > 1.Part C · Revision page
| Approach 1: costly push | Approach 2: costly pop/top | |
|---|---|---|
| order inside q1 | newest at the FRONT | arrival order, newest at the BACK |
| push | x into empty q2, move q1 behind it, swap: O(n) | append to q1: O(1) |
| pop | popleft q1: O(1) | move all but last, poll last, swap: O(n) |
| top | q1[0]: O(1) | same as pop + put the value back: O(n) |
| empty | not q1 | not q1 |
| pick it when… | pop/top are the priority | push is the priority |
| Queue from stacks (Problem 18) | Stack from queues (this problem) | |
|---|---|---|
| moving all items across | reverses order | keeps order |
| best cost | push O(1), pop amortised O(1) | one side is always O(n) |
2. Pouring one queue into another keeps the order, so it doesn't help by itself.
3. Approach 1: newcomer into the empty helper, old items behind it, swap → newest at the front.
4. Approach 2: just append; on pop/top, move all but the last to the helper, take the last, swap.
5. One of push or pop is always O(n); choose based on which you need fast.
✗ pushing the new item into q1 (the non-empty queue) in Approach 1
✗ looping
while q1 instead of while len(q1) > 1 in Approach 2✗ forgetting to put the value back in
top() of Approach 2✗ forgetting to swap q1 and q2 (or to empty q2) at the end
✗ using
deque.pop() (right end); that's a stack operation and not allowed heres = MyStack() s.push(1); s.push(2); s.push(3) print(s.pop()) # 3 s.push(4) print(s.top()) # 4 print(s.pop()) # 4 print(s.empty()) # False print(s.pop(), s.pop(), s.empty()) # 2 1 True
Based on this video: Implement Stack using Queues