Files
codetutor/backend/data/questions/maximum-depth-of-binary-tree.yaml

243 lines
8.7 KiB
YAML

title: Maximum Depth of Binary Tree
slug: maximum-depth-of-binary-tree
difficulty: easy
leetcode_id: 104
leetcode_url: https://leetcode.com/problems/maximum-depth-of-binary-tree/
categories:
- trees
- recursion
patterns:
- dfs
- bfs
- tree-traversal
function_signature: "def max_depth(root: TreeNode | None) -> int:"
test_cases:
visible:
- input: { root: [3, 9, 20, null, null, 15, 7] }
expected: 3
- input: { root: [1, null, 2] }
expected: 2
- input: { root: [] }
expected: 0
hidden:
- input: { root: [1] }
expected: 1
- input: { root: [1, 2, 3, 4, 5] }
expected: 3
- input: { root: [1, 2, null, 3, null, 4] }
expected: 4
description: |
Given the `root` of a binary tree, return *its maximum depth*.
A binary tree's **maximum depth** is the number of nodes along the longest path from the root node down to the farthest leaf node.
constraints: |
- `0 <= number of nodes <= 10^4`
- `-100 <= Node.val <= 100`
examples:
- input: "root = [3,9,20,null,null,15,7]"
output: "3"
explanation: "The tree has three levels: root (3), second level (9, 20), and third level (15, 7). The longest path has 3 nodes."
- input: "root = [1,null,2]"
output: "2"
explanation: "The tree has two levels: root (1) and its right child (2)."
explanation:
intuition: |
Think of a binary tree like a family tree where you want to find your most distant descendant.
The **maximum depth** is simply how many generations deep your family tree goes. Starting from yourself (the root), you count: child, grandchild, great-grandchild... until you reach someone with no children (a leaf node).
Here's the key insight: the depth of any node equals **1 plus the maximum depth of its subtrees**. If you're at a node, your depth is one more than whichever of your children's subtrees is deeper. This naturally leads to a recursive solution.
Alternatively, think of it like exploring floors in a building. Using BFS, you visit each floor (level) one at a time and count how many floors you traverse before running out of rooms. The number of floors visited is the maximum depth.
approach: |
We can solve this using either **DFS (recursive)** or **BFS (iterative)** approaches:
**DFS Recursive Approach**
&nbsp;
**Step 1: Handle the base case**
- If the node is `None` (empty tree or reached past a leaf), return `0`
- This is our stopping condition for recursion
&nbsp;
**Step 2: Recursively find depths of subtrees**
- Calculate `left_depth`: recursively call on the left child
- Calculate `right_depth`: recursively call on the right child
&nbsp;
**Step 3: Combine results**
- Return `1 + max(left_depth, right_depth)`
- The `1` accounts for the current node
- We take the maximum because we want the longest path
&nbsp;
**BFS Iterative Approach**
&nbsp;
**Step 1: Handle edge case**
- If root is `None`, return `0`
&nbsp;
**Step 2: Initialize BFS**
- Create a queue and add the root node
- Initialize `depth = 0`
&nbsp;
**Step 3: Process level by level**
- While the queue is not empty:
- Increment `depth` by 1 (we're processing a new level)
- Process all nodes at the current level
- Add their children to the queue for the next level
&nbsp;
**Step 4: Return the depth**
- After processing all levels, `depth` holds the maximum depth
common_pitfalls:
- title: Off-by-One Errors
description: |
A common mistake is forgetting to add `1` for the current node when combining subtree depths.
If left subtree has depth 2 and right has depth 1, the total depth isn't `max(2, 1) = 2`. It's `1 + max(2, 1) = 3` because you must count the current node too.
wrong_approach: "Return max(left_depth, right_depth)"
correct_approach: "Return 1 + max(left_depth, right_depth)"
- title: Not Handling Empty Trees
description: |
Forgetting to handle `root = None` will cause a `NoneType` error when trying to access `.left` or `.right`.
An empty tree has depth `0`, not `1`. The base case `if not root: return 0` must come first.
wrong_approach: "Assume root always exists"
correct_approach: "Check for None at the start of recursion"
- title: Confusing Depth vs Height
description: |
Some definitions count edges instead of nodes. LeetCode's definition counts **nodes** along the path.
A single-node tree has depth `1` (one node), not `0` (zero edges). Be consistent with the problem's definition.
wrong_approach: "Count edges between nodes"
correct_approach: "Count nodes along the path (LeetCode standard)"
key_takeaways:
- "**Recursive tree pattern**: Many tree problems follow the formula: handle base case, recurse on children, combine results"
- "**DFS vs BFS trade-off**: DFS uses O(h) stack space (h = height), BFS uses O(w) queue space (w = max width). For balanced trees, BFS may use more space"
- "**Foundation for harder problems**: This exact pattern extends to problems like balanced tree check, diameter of tree, and path sum"
- "**Two valid approaches**: Recognizing when both DFS and BFS can solve a problem helps you choose based on constraints"
time_complexity: "O(n). We visit every node in the tree exactly once."
space_complexity: "O(h) for DFS where h is the tree height (O(n) worst case for skewed tree, O(log n) for balanced). O(w) for BFS where w is the maximum width of the tree."
solutions:
- approach_name: DFS Recursive
is_optimal: true
code: |
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def max_depth(root: TreeNode | None) -> int:
# Base case: empty tree has depth 0
if not root:
return 0
# Recursively find depth of left and right subtrees
left_depth = max_depth(root.left)
right_depth = max_depth(root.right)
# Current depth = 1 (this node) + deeper subtree
return 1 + max(left_depth, right_depth)
explanation: |
**Time Complexity:** O(n) — Visit each node once.
**Space Complexity:** O(h) — Recursion stack depth equals tree height. Worst case O(n) for a skewed tree, O(log n) for a balanced tree.
This elegant recursive solution embodies the tree traversal pattern: solve for children, then combine. The base case handles empty subtrees, and the recursive case builds up the answer from the bottom.
- approach_name: BFS Level Order
is_optimal: true
code: |
from collections import deque
def max_depth(root: TreeNode | None) -> int:
# Empty tree has depth 0
if not root:
return 0
queue = deque([root])
depth = 0
while queue:
# Process all nodes at current level
depth += 1
level_size = len(queue)
for _ in range(level_size):
node = queue.popleft()
# Add children for next level
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return depth
explanation: |
**Time Complexity:** O(n) — Visit each node once.
**Space Complexity:** O(w) — Queue holds at most one level. For a complete binary tree, the last level has ~n/2 nodes, so O(n) in the worst case.
BFS processes the tree level by level. Each iteration of the outer while loop processes one complete level, incrementing depth. When the queue empties, we've counted all levels.
- approach_name: DFS Iterative with Stack
is_optimal: false
code: |
def max_depth(root: TreeNode | None) -> int:
if not root:
return 0
# Stack stores (node, current_depth) pairs
stack = [(root, 1)]
max_depth_found = 0
while stack:
node, depth = stack.pop()
max_depth_found = max(max_depth_found, depth)
# Add children with incremented depth
if node.left:
stack.append((node.left, depth + 1))
if node.right:
stack.append((node.right, depth + 1))
return max_depth_found
explanation: |
**Time Complexity:** O(n) — Visit each node once.
**Space Complexity:** O(h) — Stack depth equals tree height.
This iterative DFS explicitly manages the stack that recursion handles implicitly. We track each node's depth as we traverse, updating the maximum when we visit each node. Useful when recursion depth might cause stack overflow.