Files
codetutor/backend/data/questions/implement-stack-using-queues.yaml

236 lines
9.9 KiB
YAML

title: Implement Stack using Queues
slug: implement-stack-using-queues
difficulty: easy
leetcode_id: 225
leetcode_url: https://leetcode.com/problems/implement-stack-using-queues/
categories:
- stack
- queue
patterns:
- slug: monotonic-stack
is_optimal: true
function_signature: "class MyStack"
test_cases:
visible:
- input: { operations: ["MyStack", "push", "push", "top", "pop", "empty"], arguments: [[], [1], [2], [], [], []] }
expected: [null, null, null, 2, 2, false]
hidden:
- input: { operations: ["MyStack", "push", "top"], arguments: [[], [5], []] }
expected: [null, null, 5]
- input: { operations: ["MyStack", "push", "push", "push", "pop", "pop", "pop"], arguments: [[], [1], [2], [3], [], [], []] }
expected: [null, null, null, null, 3, 2, 1]
- input: { operations: ["MyStack", "push", "pop", "push", "pop"], arguments: [[], [1], [], [2], []] }
expected: [null, null, 1, null, 2]
- input: { operations: ["MyStack", "empty"], arguments: [[], []] }
expected: [null, true]
- input: { operations: ["MyStack", "push", "push", "pop", "push", "top"], arguments: [[], [1], [2], [], [3], []] }
expected: [null, null, null, 2, null, 3]
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 element `x` to 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()` Returns `true` if the stack is empty, `false` otherwise.
**Notes:**
- You must use **only** standard operations of a queue, which means only `push to back`, `peek/pop from front`, `size`, and `is empty` operations 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?
constraints: |
- `1 <= x <= 9`
- At most `100` calls will be made to `push`, `pop`, `top`, and `empty`
- All the calls to `pop` and `top` are valid
examples:
- 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] (rightmost is top of stack)
myStack.top(); // return 2
myStack.pop(); // return 2, stack is [1]
myStack.empty(); // return false
explanation:
intuition: |
Think of this problem like having a queue of people, but you want the *last* person who joined to be served first — the opposite of how a normal queue works.
A **stack** follows Last-In-First-Out (LIFO) order — the most recently added item is the first to leave, like a stack of plates. A **queue** follows First-In-First-Out (FIFO) order — the first item added is the first to leave, like a line at a coffee shop.
The key insight is that **rotating a queue puts the back element at the front**. If we push a new element to a queue and then rotate all the *other* elements behind it (by dequeuing from front and enqueuing to back), the new element ends up at the front — exactly where we need it for LIFO access!
For example, if the queue contains `[1, 2]` (front to back) and we push `3`:
1. Queue becomes `[1, 2, 3]`
2. Rotate twice: dequeue `1`, enqueue `1` → `[2, 3, 1]`
3. Rotate again: dequeue `2`, enqueue `2` → `[3, 1, 2]`
Now `3` (the most recent) is at the front, ready to be popped first!
approach: |
We solve this using a **Single Queue with Rotation** approach:
**Step 1: Initialise one queue**
- `queue`: A deque used with only queue operations (append to back, pop from front)
&nbsp;
**Step 2: Push operation**
- Append the new element to the back of the queue
- Rotate the queue by moving all *previous* elements behind the new one
- Specifically: pop from front and append to back, `n-1` times (where `n` is the current size)
- After rotation, the new element is at the front
&nbsp;
**Step 3: Pop operation**
- Simply pop from the front of the queue
- Since we maintain LIFO order, the front element is always the most recently pushed
&nbsp;
**Step 4: Top operation**
- Return the front element without removing it
- Use index access `queue[0]` or peek operation
&nbsp;
**Step 5: Empty check**
- Return whether the queue is empty
&nbsp;
This approach makes push O(n) but keeps pop and top at O(1), which is often preferable since pops are typically more frequent than pushes.
common_pitfalls:
- title: Rotating the Wrong Number of Times
description: |
When pushing a new element, you need to rotate exactly `n-1` elements (the elements that were already in the queue before the push), not `n` elements.
If you rotate `n` times, you'll move the new element to the back again, undoing the work:
- Push `3` to `[1, 2]` → `[1, 2, 3]`
- Rotate 3 times: `[2, 3, 1]` → `[3, 1, 2]` → `[1, 2, 3]` (back to original!)
The correct rotation count is `len(queue) - 1` after appending.
wrong_approach: "Rotate n times after pushing"
correct_approach: "Rotate n-1 times (previous size) after pushing"
- title: Using Two Queues Unnecessarily
description: |
The problem mentions "two queues", but the follow-up asks if you can do it with one. Many solutions use two queues and swap between them, which adds complexity without benefit.
The single-queue rotation approach is simpler and equally efficient. The two-queue approach might seem more intuitive at first, but it requires more bookkeeping.
- title: Confusing Queue Operations with Deque Operations
description: |
In Python, `collections.deque` supports both `appendleft` and `append`, but a true queue only allows:
- `append` (enqueue to back)
- `popleft` (dequeue from front)
- `len` and checking if empty
Using `appendleft` or `pop` (from back) violates the "queue only" constraint. Make sure your solution only uses valid queue operations.
key_takeaways:
- "**Data structure simulation**: You can simulate one data structure with another by understanding their ordering properties"
- "**Rotation technique**: Moving elements from front to back of a queue is a powerful way to reorder elements"
- "**Trade-off decisions**: Making push expensive (O(n)) keeps pop/top cheap (O(1)) — choose based on expected usage patterns"
- "**Related problem**: The inverse problem — implementing a queue using stacks (LeetCode 232) — uses a similar reversal concept"
time_complexity: "O(n) for push, O(1) for pop/top/empty. Each push rotates n-1 elements, while other operations access the front directly."
space_complexity: "O(n). We store all n elements in a single queue."
solutions:
- approach_name: Single Queue with Rotation
is_optimal: true
code: |
from collections import deque
class MyStack:
def __init__(self):
# Single queue to simulate stack
self.queue = deque()
def push(self, x: int) -> None:
# Add new element to back
self.queue.append(x)
# Rotate all previous elements behind it
# This puts the new element at the front
for _ in range(len(self.queue) - 1):
self.queue.append(self.queue.popleft())
def pop(self) -> int:
# Front of queue is top of stack (most recent)
return self.queue.popleft()
def top(self) -> int:
# Return front without removing
return self.queue[0]
def empty(self) -> bool:
return len(self.queue) == 0
explanation: |
**Time Complexity:**
- `push`: O(n) — rotate n-1 elements
- `pop`: O(1) — remove from front
- `top`: O(1) — access front element
- `empty`: O(1) — check length
**Space Complexity:** O(n) — storing n elements in the queue.
The key insight is rotating during push: after adding a new element to the back, we cycle all previous elements behind it. This ensures the most recently pushed element is always at the front, ready for O(1) pop/top access.
- approach_name: Two Queues with Transfer
is_optimal: false
code: |
from collections import deque
class MyStack:
def __init__(self):
self.q1 = deque() # Main queue
self.q2 = deque() # Temporary queue
def push(self, x: int) -> None:
# Push to temporary queue first
self.q2.append(x)
# Move all elements from q1 to q2
while self.q1:
self.q2.append(self.q1.popleft())
# Swap queues - q2 becomes the new main queue
self.q1, self.q2 = self.q2, self.q1
def pop(self) -> int:
# Front of q1 is top of stack
return self.q1.popleft()
def top(self) -> int:
return self.q1[0]
def empty(self) -> bool:
return len(self.q1) == 0
explanation: |
**Time Complexity:**
- `push`: O(n) — transfer all elements
- `pop`: O(1) — remove from front
- `top`: O(1) — access front element
- `empty`: O(1) — check length
**Space Complexity:** O(n) — storing n elements across two queues.
This approach uses two queues. On each push, we add the new element to an empty queue, then transfer all elements from the main queue. This puts the new element at the front. While correct, the single-queue rotation approach is simpler.