-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path0104_maximum_depth_of_binary_tree.py
More file actions
156 lines (124 loc) · 4.77 KB
/
Copy path0104_maximum_depth_of_binary_tree.py
File metadata and controls
156 lines (124 loc) · 4.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""104. Maximum Depth of Binary Tree — Easy
LeetCode: https://leetcode.com/problems/maximum-depth-of-binary-tree/
Problem
-------
Given the root of a binary tree, return the length of its longest root-to-leaf
path measured in number of nodes (not edges). A leaf is a node with no
children. The depth of an empty tree is 0.
Examples
--------
Input: root = [3, 9, 20, null, null, 15, 7]
Output: 3
Explanation: The path 3 → 20 → 7 (or 3 → 20 → 15) has 3 nodes.
Input: root = [1, null, 2]
Output: 2
Intuition
---------
The depth of a node equals 1 plus the maximum depth of its two subtrees.
Ask each subtree for its depth, take the larger, add one — that is the full
recursion in one sentence.
Approach
--------
1. Base case: an empty node (None) has depth 0.
2. Recursive case: recurse left and right, return 1 + max(left_depth,
right_depth).
Each recursive call returns "the deepest I can reach from here", so the root
call returns the answer directly — this is the classic "return value" DFS
framing.
Brute force would be identical; there is no inefficiency to remove. BFS
level-order traversal is an alternative: push nodes level by level and count
levels until the queue empties. Recursive DFS is simpler code.
Complexity
----------
Time: O(n) every node is visited exactly once.
Space: O(h) the call stack depth equals the tree height h; O(log n) for a
balanced tree, O(n) worst-case for a skewed tree.
Edge cases
----------
- Empty tree (root is None) → return 0.
- Single node → return 1.
- Completely skewed tree (linked-list shape) → depth equals n.
Pattern: DFS Recursion (post-order), BFS level-order (alternative).
ML relevance: Decision-tree max depth is a direct hyperparameter; this
function computes exactly that value for any tree.
"""
from __future__ import annotations
from collections import deque
from typing import Optional
class TreeNode:
"""Standard LeetCode binary-tree node."""
def __init__(
self,
val: int = 0,
left: Optional[TreeNode] = None,
right: Optional[TreeNode] = None,
) -> None:
self.val = val
self.left = left
self.right = right
class Solution:
"""LeetCode submission class."""
def maxDepth(self, root: Optional[TreeNode]) -> int: # noqa: N802
"""Return the maximum depth using post-order DFS recursion."""
if root is None:
return 0
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
def max_depth_bfs(self, root: Optional[TreeNode]) -> int:
"""Return the maximum depth using BFS level-order traversal.
Alternative approach: count levels as we sweep the tree breadth-first.
Same O(n) time but O(w) space where w is the maximum level width.
"""
if root is None:
return 0
depth = 0
queue: deque[TreeNode] = deque([root])
while queue:
depth += 1
for _ in range(len(queue)):
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return depth
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _build_tree(values: list[int | None]) -> Optional[TreeNode]:
"""Build a binary tree from a LeetCode-style level-order list."""
if not values or values[0] is None:
return None
root = TreeNode(values[0])
queue: deque[TreeNode] = deque([root])
index = 1
while queue and index < len(values):
node = queue.popleft()
if index < len(values) and values[index] is not None:
node.left = TreeNode(values[index]) # type: ignore[arg-type]
queue.append(node.left)
index += 1
if index < len(values) and values[index] is not None:
node.right = TreeNode(values[index]) # type: ignore[arg-type]
queue.append(node.right)
index += 1
return root
def _demo() -> None:
"""Run worked examples as assertions."""
solver = Solution()
# [3, 9, 20, null, null, 15, 7] → depth 3
root1 = _build_tree([3, 9, 20, None, None, 15, 7])
assert solver.maxDepth(root1) == 3
assert solver.max_depth_bfs(root1) == 3
# [1, null, 2] → depth 2
root2 = _build_tree([1, None, 2])
assert solver.maxDepth(root2) == 2
assert solver.max_depth_bfs(root2) == 2
# Empty tree → 0
assert solver.maxDepth(None) == 0
assert solver.max_depth_bfs(None) == 0
# Single node → 1
root3 = _build_tree([42])
assert solver.maxDepth(root3) == 1
print("All tests passed.")
if __name__ == "__main__":
_demo()