Files
codetutor/backend/data/questions/binary-tree-level-order-traversal-ii.yaml

223 lines
9.1 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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:
- slug: bfs
is_optimal: true
- slug: tree-traversal
is_optimal: false
function_signature: "def level_order_bottom(root: TreeNode) -> list[list[int]]:"
test_cases:
visible:
- input: { root: [3, 9, 20, null, null, 15, 7] }
expected: [[15, 7], [9, 20], [3]]
- input: { root: [1] }
expected: [[1]]
- input: { root: [] }
expected: []
hidden:
- input: { root: [1, 2, 3, 4, 5, 6, 7] }
expected: [[4, 5, 6, 7], [2, 3], [1]]
- input: { root: [1, 2, null, 3, null, 4] }
expected: [[4], [3], [2], [1]]
- input: { root: [1, null, 2, null, 3] }
expected: [[3], [2], [1]]
- input: { root: [-10, 9, 20, null, null, 15, 7] }
expected: [[15, 7], [9, 20], [-10]]
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
&nbsp;
**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
&nbsp;
**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
&nbsp;
**Step 4: Reverse and return**
- Reverse the result list so the deepest level comes first
- Return the reversed result
&nbsp;
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.