questions B (backspace - burst-balloons)

This commit is contained in:
2025-05-24 22:06:49 +01:00
parent 9eaafe4649
commit 1e0aebfbfd
67 changed files with 13945 additions and 0 deletions

View File

@@ -0,0 +1,229 @@
title: Binary Tree Zigzag Level Order Traversal
slug: binary-tree-zigzag-level-order-traversal
difficulty: medium
leetcode_id: 103
leetcode_url: https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/
categories:
- trees
- queue
patterns:
- bfs
- tree-traversal
description: |
Given the `root` of a binary tree, return *the zigzag level order traversal of its nodes' values* (i.e., from left to right, then right to left for the next level and alternate between).
constraints: |
- The number of nodes in the tree is in the range `[0, 2000]`
- `-100 <= Node.val <= 100`
examples:
- input: "root = [3,9,20,null,null,15,7]"
output: "[[3],[20,9],[15,7]]"
explanation: "Level 0 (left to right): [3]. Level 1 (right to left): [20, 9]. Level 2 (left to right): [15, 7]."
- input: "root = [1]"
output: "[[1]]"
explanation: "Single node tree returns one level with one element."
- input: "root = []"
output: "[]"
explanation: "Empty tree returns an empty list."
explanation:
intuition: |
Imagine reading a book where alternating pages are printed upside down. To read the content, you'd read one page normally (left to right), then flip the book to read the next page (effectively right to left), and keep alternating.
This problem applies the same concept to tree traversal. A standard **level order traversal** (BFS) visits nodes level by level, always left to right. The "zigzag" twist requires us to **alternate the direction** at each level: left-to-right, then right-to-left, then left-to-right again, and so on.
The core insight is that the underlying traversal is still BFS — we process nodes level by level using a queue. The only change is how we **arrange the values within each level**. We can either reverse every other level's values, or use a deque to build each level's list in the appropriate direction.
Think of it like this: BFS gives us nodes in consistent left-to-right order. We just need a simple flag to track whether the current level should be reversed, and apply that transformation before adding to the result.
approach: |
We solve this using **BFS with level direction tracking**:
**Step 1: Handle the empty tree edge case**
- If `root` is `None`, return an empty list immediately
&nbsp;
**Step 2: Initialise BFS structures**
- `queue`: A deque starting with the root node for level-order traversal
- `result`: An empty list to store the final zigzag traversal
- `left_to_right`: A boolean flag set to `True`, indicating the direction for the current level
&nbsp;
**Step 3: Process level by level**
- While the queue is not empty:
- Record the current level size (number of nodes at this level)
- Create an empty list `level` to hold values for this level
- Process all nodes at the current level:
- Dequeue each node from the front
- Append its value to `level`
- Enqueue its left child (if exists), then right child (if exists)
- If `left_to_right` is `False`, reverse the `level` list
- Append `level` to `result`
- Toggle `left_to_right` for the next level
&nbsp;
**Step 4: Return the result**
- Return `result` containing all levels in zigzag order
&nbsp;
The key is that we always traverse children left-to-right in the queue, but we reverse the values of alternate levels before adding them to the result. This keeps the BFS logic clean and simple.
common_pitfalls:
- title: Modifying Queue Order Instead of Output
description: |
A tempting approach is to change the order of adding children to the queue based on direction — adding right child first for even levels, left child first for odd levels.
This **breaks the traversal** for subsequent levels. If you add children in reverse order, the next level receives nodes in the wrong sequence, causing cascading errors.
The correct approach keeps queue insertion order consistent (always left then right) and only reverses the output values for alternate levels.
wrong_approach: "Alternate which child gets enqueued first"
correct_approach: "Always enqueue left then right, reverse output on alternate levels"
- title: Using a Stack Instead of Reversing
description: |
Some solutions try using a stack for zigzag levels. While this can work, it significantly complicates the code and is prone to off-by-one errors in tracking which structure to use.
Reversing a level list in Python is O(k) where k is the level size, and since we visit each node exactly once overall, this doesn't change the O(n) time complexity. The simpler approach is almost always better.
- title: Forgetting the Empty Tree Case
description: |
If `root` is `None`, attempting to add it to the queue and process it will cause errors. Always check for an empty tree at the start and return `[]` immediately.
key_takeaways:
- "**BFS for level-order**: Use a queue to process nodes level by level, tracking level boundaries with the queue's size"
- "**Separate traversal from transformation**: Keep the core BFS logic clean; apply direction changes only to the output"
- "**Flag-based alternation**: A simple boolean toggle handles the zigzag pattern elegantly"
- "**Foundation for variants**: This pattern extends to spiral matrix traversal and other alternating-direction problems"
time_complexity: "O(n). We visit each of the `n` nodes exactly once during traversal. Reversing levels takes O(n) total across all levels."
space_complexity: "O(n). The queue holds at most one level of nodes at a time (up to `n/2` in a complete binary tree), and the result stores all `n` node values."
solutions:
- approach_name: BFS with Level Reversal
is_optimal: true
code: |
from collections import deque
def zigzag_level_order(root: TreeNode | None) -> list[list[int]]:
# Handle empty tree
if not root:
return []
result = []
queue = deque([root])
left_to_right = True # First level goes left to right
while queue:
level_size = len(queue)
level = []
# Process all nodes at current level
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
# Always add children left to right
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
# Reverse if this level should go right to left
if not left_to_right:
level.reverse()
result.append(level)
left_to_right = not left_to_right # Toggle direction
return result
explanation: |
**Time Complexity:** O(n) — Each node is visited once, and reversing all levels takes O(n) total.
**Space Complexity:** O(n) — Queue holds at most one level (up to n/2 nodes), result stores all n values.
This approach uses standard BFS with a direction flag. We always process children left-to-right in the queue, but reverse the values of alternate levels before adding to the result. This keeps the code simple and avoids the complexity of managing different queue orderings.
- approach_name: BFS with Deque Insertion
is_optimal: true
code: |
from collections import deque
def zigzag_level_order(root: TreeNode | None) -> list[list[int]]:
if not root:
return []
result = []
queue = deque([root])
left_to_right = True
while queue:
level_size = len(queue)
level = deque() # Use deque to build level in correct order
for _ in range(level_size):
node = queue.popleft()
# Insert at appropriate end based on direction
if left_to_right:
level.append(node.val) # Add to right
else:
level.appendleft(node.val) # Add to left
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(list(level))
left_to_right = not left_to_right
return result
explanation: |
**Time Complexity:** O(n) — Each node visited once, deque operations are O(1).
**Space Complexity:** O(n) — Same as the reversal approach.
Instead of reversing after collecting values, this approach builds each level's list in the correct order using a deque. For left-to-right levels, we append to the right; for right-to-left levels, we prepend to the left. Both approaches are equally optimal.
- approach_name: Recursive DFS with Level Tracking
is_optimal: false
code: |
def zigzag_level_order(root: TreeNode | None) -> list[list[int]]:
result = []
def dfs(node: TreeNode | None, level: int) -> None:
if not node:
return
# Extend result if we've reached a new level
if level >= len(result):
result.append([])
# Insert based on level parity
if level % 2 == 0:
result[level].append(node.val) # Left to right
else:
result[level].insert(0, node.val) # Right to left
# Recurse on children
dfs(node.left, level + 1)
dfs(node.right, level + 1)
dfs(root, 0)
return result
explanation: |
**Time Complexity:** O(n^2) in worst case — `insert(0, val)` is O(k) for a list of size k, making odd levels costly.
**Space Complexity:** O(n) for result plus O(h) recursion stack where h is tree height.
This DFS approach tracks the current level and inserts values accordingly. While elegant, using `insert(0, val)` on a list is O(k), making this less efficient than BFS approaches for large trees. Included to show an alternative traversal strategy.