questions B (backspace - burst-balloons)
This commit is contained in:
200
backend/data/questions/binary-tree-level-order-traversal-ii.yaml
Normal file
200
backend/data/questions/binary-tree-level-order-traversal-ii.yaml
Normal file
@@ -0,0 +1,200 @@
|
||||
title: Binary Tree Level Order Traversal II
|
||||
slug: binary-tree-level-order-traversal-ii
|
||||
difficulty: medium
|
||||
leetcode_id: 107
|
||||
leetcode_url: https://leetcode.com/problems/binary-tree-level-order-traversal-ii/
|
||||
categories:
|
||||
- trees
|
||||
- queue
|
||||
patterns:
|
||||
- bfs
|
||||
- tree-traversal
|
||||
|
||||
description: |
|
||||
Given the `root` of a binary tree, return *the bottom-up level order traversal of its nodes' values* (i.e., from left to right, level by level from leaf to root).
|
||||
|
||||
constraints: |
|
||||
- The number of nodes in the tree is in the range `[0, 2000]`
|
||||
- `-1000 <= Node.val <= 1000`
|
||||
|
||||
examples:
|
||||
- input: "root = [3,9,20,null,null,15,7]"
|
||||
output: "[[15,7],[9,20],[3]]"
|
||||
explanation: "The deepest level (15, 7) comes first, then level 1 (9, 20), and finally the root level (3)."
|
||||
- input: "root = [1]"
|
||||
output: "[[1]]"
|
||||
explanation: "Single node forms its own level."
|
||||
- input: "root = []"
|
||||
output: "[]"
|
||||
explanation: "Empty tree returns an empty list."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
This problem is a twist on the classic level order traversal. Instead of reading the tree from top to bottom, we want to read it from bottom to top — like viewing a family photo starting with the grandchildren and working up to the grandparents.
|
||||
|
||||
Think of it like this: if we already know how to do a standard level order traversal (BFS), we get levels in the order `[root, level1, level2, ...]`. To get the bottom-up order, we simply **reverse the result** at the end: `[level2, level1, root]`.
|
||||
|
||||
The key insight is that **we don't need a fundamentally different algorithm**. BFS naturally processes the tree level by level from top to bottom. Rather than trying to traverse bottom-up (which is awkward), we traverse top-down and then reverse. This is both simpler and equally efficient.
|
||||
|
||||
Alternatively, we can build the result in reverse order by inserting each level at the front of the result list, but this is less efficient in most languages.
|
||||
|
||||
approach: |
|
||||
We solve this using **BFS with Final Reversal**:
|
||||
|
||||
**Step 1: Handle the empty tree case**
|
||||
|
||||
- If `root` is `None`, return an empty list immediately
|
||||
|
||||
|
||||
|
||||
**Step 2: Initialise the queue and result**
|
||||
|
||||
- Create a queue starting with just the root node
|
||||
- Create an empty result list to hold each level's values
|
||||
|
||||
|
||||
|
||||
**Step 3: Process level by level (standard BFS)**
|
||||
|
||||
- While the queue is not empty:
|
||||
- Capture `level_size = len(queue)` — this is how many nodes are in the current level
|
||||
- Create a `level_values` list for this level
|
||||
- Process exactly `level_size` nodes:
|
||||
- Dequeue a node, add its value to `level_values`
|
||||
- Enqueue its left child (if exists)
|
||||
- Enqueue its right child (if exists)
|
||||
- Append `level_values` to the result
|
||||
|
||||
|
||||
|
||||
**Step 4: Reverse and return**
|
||||
|
||||
- Reverse the result list so the deepest level comes first
|
||||
- Return the reversed result
|
||||
|
||||
|
||||
|
||||
The reversal is O(n) and doesn't change the overall complexity. This approach is clean because it reuses the well-known BFS level order pattern.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Over-Engineering the Traversal
|
||||
description: |
|
||||
A common mistake is trying to traverse the tree from bottom to top directly. This is unnecessarily complex — you'd need to first find the depth of the tree, then process nodes by depth in reverse order.
|
||||
|
||||
The simpler approach: do standard BFS (which is well-understood and easy to implement), then reverse the result at the end.
|
||||
wrong_approach: "Trying to traverse bottom-up directly"
|
||||
correct_approach: "Standard BFS, then reverse the result"
|
||||
|
||||
- title: Inserting at Front Instead of Reversing
|
||||
description: |
|
||||
Some solutions insert each level at index 0 of the result list: `result.insert(0, level_values)`. While this produces the correct output, `insert(0, ...)` on a Python list is O(n) for each insertion.
|
||||
|
||||
With k levels, this leads to O(n × k) operations. Appending and reversing at the end is O(n) total.
|
||||
wrong_approach: "result.insert(0, level_values) for each level"
|
||||
correct_approach: "result.append(level_values), then result.reverse() at the end"
|
||||
|
||||
- title: Using List as Queue
|
||||
description: |
|
||||
In Python, `list.pop(0)` is O(n) because all remaining elements must shift. For a tree with n nodes, this makes the algorithm O(n²) instead of O(n).
|
||||
|
||||
Use `collections.deque` which has O(1) `popleft()`.
|
||||
wrong_approach: "queue = []; queue.pop(0)"
|
||||
correct_approach: "queue = deque(); queue.popleft()"
|
||||
|
||||
- title: Forgetting the Empty Tree Check
|
||||
description: |
|
||||
If `root` is `None`, the queue starts empty, and the while loop never executes — but some implementations might crash trying to access root.
|
||||
|
||||
Always check `if not root: return []` at the start.
|
||||
wrong_approach: "Assuming root is never None"
|
||||
correct_approach: "if not root: return []"
|
||||
|
||||
key_takeaways:
|
||||
- "**Transform, don't reinvent**: When a problem is a variation of a classic pattern, solve the classic version and transform the output"
|
||||
- "**BFS = level order**: Breadth-first search naturally processes trees level by level — master this fundamental pattern"
|
||||
- "**Reversal is cheap**: Reversing a list is O(n), same as the traversal — don't avoid it to pursue a 'clever' but slower solution"
|
||||
- "**Same pattern, many problems**: This technique extends to zigzag traversal, right side view, level averages, and more"
|
||||
|
||||
time_complexity: "O(n). Every node is visited exactly once, and the final reversal is also O(n)."
|
||||
space_complexity: "O(n). The queue holds at most one level of nodes (~n/2 in a complete binary tree), and the result stores all n node values."
|
||||
|
||||
solutions:
|
||||
- approach_name: BFS with Reversal
|
||||
is_optimal: true
|
||||
code: |
|
||||
from collections import deque
|
||||
|
||||
class TreeNode:
|
||||
def __init__(self, val=0, left=None, right=None):
|
||||
self.val = val
|
||||
self.left = left
|
||||
self.right = right
|
||||
|
||||
def level_order_bottom(root: TreeNode | None) -> list[list[int]]:
|
||||
# Handle empty tree
|
||||
if not root:
|
||||
return []
|
||||
|
||||
result = []
|
||||
queue = deque([root])
|
||||
|
||||
while queue:
|
||||
# Capture current level size BEFORE processing
|
||||
level_size = len(queue)
|
||||
level_values = []
|
||||
|
||||
# Process exactly level_size nodes (all nodes at current level)
|
||||
for _ in range(level_size):
|
||||
node = queue.popleft()
|
||||
level_values.append(node.val)
|
||||
|
||||
# Add children for next level
|
||||
if node.left:
|
||||
queue.append(node.left)
|
||||
if node.right:
|
||||
queue.append(node.right)
|
||||
|
||||
# This level is complete
|
||||
result.append(level_values)
|
||||
|
||||
# Reverse to get bottom-up order
|
||||
result.reverse()
|
||||
return result
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Each node added and removed from queue exactly once, plus O(n) for the reversal.
|
||||
|
||||
**Space Complexity:** O(n) — Queue can hold up to n/2 nodes, and result stores all n values.
|
||||
|
||||
Standard BFS processes levels top-to-bottom. We collect each level, then reverse the result at the end. The reversal adds O(n) time but doesn't change the overall complexity.
|
||||
|
||||
- approach_name: DFS with Level Tracking
|
||||
is_optimal: false
|
||||
code: |
|
||||
def level_order_bottom(root: TreeNode | None) -> list[list[int]]:
|
||||
result = []
|
||||
|
||||
def dfs(node: TreeNode | None, level: int) -> None:
|
||||
if not node:
|
||||
return
|
||||
|
||||
# First time at this level? Create new list
|
||||
if level == len(result):
|
||||
result.append([])
|
||||
|
||||
# Add current node to its level
|
||||
result[level].append(node.val)
|
||||
|
||||
# Recurse to children (level + 1)
|
||||
dfs(node.left, level + 1)
|
||||
dfs(node.right, level + 1)
|
||||
|
||||
dfs(root, 0)
|
||||
# Reverse to get bottom-up order
|
||||
result.reverse()
|
||||
return result
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — Visit each node once, plus O(n) for reversal.
|
||||
|
||||
**Space Complexity:** O(h) — Recursion stack depth equals tree height (O(log n) for balanced, O(n) for skewed).
|
||||
|
||||
DFS alternative: pass the level as a parameter. Each node appends to its level's list. Left-to-right order is preserved because we recurse left before right. Finally, reverse the result for bottom-up order.
|
||||
Reference in New Issue
Block a user