Description
Given the root of a binary tree, return its maximum depth.
The maximum depth of a binary tree is the number of nodes along the longest path from the root node down to the farthest leaf node.
A leaf node is a node that does not have any children.
Example 1
Input: root = [1,2,3,null,null,4]
Output: 3The longest path is:
1 → 3 → 4This path contains three nodes, so the maximum depth is 3.
Example 2
Input: root = []
Output: 0An empty tree does not contain any nodes, so its depth is 0.
Constraints
0 <= number of nodes in the tree <= 100
-100 <= Node.val <= 100Approach 1: Recursive Depth-First Search
The most natural way to solve this problem is with recursion.
For every node, we calculate:
- The depth of its left subtree
- The depth of its right subtree
We take the greater of the two depths and add 1 for the current node.
The recurrence is:
depth(node) = 1 + max(depth(node.left), depth(node.right))If the current node is None, its depth is 0.
Code
from typing import Optional
# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
return 1 + max(
self.maxDepth(root.left),
self.maxDepth(root.right)
)How It Works
Consider the following tree:
1
/ \
2 3
\
4Starting at node 1, the function asks for the depth of the left and right subtrees.
For node 2:
left depth = 0
right depth = 0Therefore:
depth of node 2 = 1 + max(0, 0) = 1For node 4:
depth of node 4 = 1For node 3:
left depth = 0
right depth = 1Therefore:
depth of node 3 = 1 + max(0, 1) = 2Finally, for node 1:
left depth = 1
right depth = 2Therefore:
depth of node 1 = 1 + max(1, 2) = 3Why We Add
1
The recursive calls calculate the depth below the current node.
The 1 represents the current node itself.
1 + max(left_depth, right_depth)Without adding 1, the current node would not be included in the path length.
Complexity
Time Complexity
O(n)Every node is visited exactly once.
Space Complexity
O(h)Here, h is the height of the tree.
The recursive call stack stores one function call for each node along the current path.
For a balanced tree:
O(log n)For a completely unbalanced tree:
O(n)Approach 2: Breadth-First Search
Breadth-first search processes the tree one level at a time.
We use a queue to store the nodes waiting to be processed.
Each time we finish processing one complete level, we increase the depth by 1.
Code
from typing import Optional
from collections import deque
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
level = 0
queue = deque([root])
while queue:
for _ in range(len(queue)):
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
level += 1
return levelHow It Works
We begin by adding the root node to the queue:
queue = deque([root])For the following tree:
1
/ \
2 3
\
4The queue changes like this:
Level 1
Queue: [1]We remove node 1 and add its children:
Queue: [2, 3]After processing the level:
level = 1Level 2
At the beginning of the iteration:
Queue: [2, 3]We remove exactly two nodes because the queue currently contains two nodes.
After processing nodes 2 and 3:
Queue: [4]
level = 2Level 3
Queue: [4]After processing node 4:
Queue: []
level = 3The queue is now empty, so we return 3.
Why We Use
range(len(queue))
This line is important:
for _ in range(len(queue)):At the start of each while loop iteration, len(queue) represents the number of nodes in the current level.
New child nodes may be added to the queue while the loop is running, but they should not be processed until the next level.
Python evaluates len(queue) before the for loop starts. Therefore, only the nodes that were already in the queue are processed during the current iteration.
Why deque Is Used
We repeatedly remove nodes from the front of the queue:
node = queue.popleft()A deque supports removing from the front in:
O(1)Removing the first element from a normal Python list would take:
O(n)because all remaining elements would need to shift one position.
Complexity
Time Complexity
O(n)Every node is added to and removed from the queue once.
Space Complexity
O(w)Here, w is the maximum width of the tree.
In the worst case, the queue may contain an entire level of the tree, resulting in:
O(n)space.
Approach 3: Iterative Depth-First Search
We can also perform depth-first search iteratively using a stack.
The stack stores both:
- The node that should be processed
- The depth of that node
Each stack entry has the form:
(node, depth)Code
from typing import Optional
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
if not root:
return 0
stack = [(root, 1)]
result = 0
while stack:
node, depth = stack.pop()
result = max(result, depth)
if node.left:
stack.append((node.left, depth + 1))
if node.right:
stack.append((node.right, depth + 1))
return resultHow It Works
We begin by placing the root in the stack with a depth of 1:
stack = [(root, 1)]The root has depth 1 because it is the first node in the path.
On every iteration, we remove the most recently added item:
node, depth = stack.pop()We then update the greatest depth seen so far:
result = max(result, depth)Finally, we add the node’s children with a depth that is one greater:
stack.append((node.left, depth + 1))
stack.append((node.right, depth + 1))Example
Consider:
1
/ \
2 3
\
4The stack begins as:
[(1, 1)]After processing node 1, we add its children:
[(2, 2), (3, 2)]Because a stack follows last-in, first-out order, node 3 is processed next.
After processing node 3, its children are added:
[(2, 2), (None, 3), (4, 3)]When node 4 is processed:
result = max(2, 3)Therefore:
result = 3Why Store the Depth in the Stack?
A normal recursive DFS automatically remembers the current depth through the recursive call stack.
With an iterative DFS, we do not have recursive function calls to track that information.
Therefore, we manually store the depth alongside each node:
(node, depth)This allows us to know how far each node is from the root when it is removed from the stack.
Why Can Tuples Be Unpacked?
The stack contains tuples such as:
(root, 1)When we write:
node, depth = stack.pop()Python removes one tuple from the stack and unpacks its two values.
For example:
item = (root, 1)
node, depth = itemThis assigns:
node = root
depth = 1Lists could also be used:
stack = [[root, 1]]Both lists and tuples can be unpacked.
A tuple is slightly more appropriate here because each stack entry is a fixed pair of values that does not need to be modified.
Complexity
Time Complexity
O(n)Every node is processed once.
Space Complexity
O(h)The stack stores nodes along the DFS traversal.
For a balanced tree:
O(log n)For a completely unbalanced tree:
O(n)Because this implementation temporarily adds None children to the stack, it may store some additional entries, but the overall complexity remains the same.
Comparing the Three Approaches
Approach | Data structure | Time | Space | Main idea |
|---|---|---|---|---|
Recursive DFS | Recursion stack |
|
| Find the greater subtree depth |
BFS | Queue |
|
| Count the number of levels |
Iterative DFS | Explicit stack |
|
| Track each node with its depth |
Here:
nis the number of nodeshis the height of the treewis the maximum width of the tree
Which Approach Should I Use?
The recursive DFS solution is usually the best solution to present first.
It is concise and directly expresses the recursive structure of a binary tree:
return 1 + max(
self.maxDepth(root.left),
self.maxDepth(root.right)
)The BFS solution is useful when thinking in terms of tree levels.
The iterative DFS solution is useful when recursion is not allowed or when the tree may be deep enough to cause a recursion-depth issue.
Improved Iterative DFS Version
The original iterative DFS solution adds if not root to the start of the code.
if not root:
return 0We can avoid it by checking the node before updating the result.
class Solution:
def maxDepth(self, root: Optional[TreeNode]) -> int:
stack = [(root, 1)]
result = 0
while stack:
node, depth = stack.pop()
if node:
result = max(result, depth)
stack.append((node.left, depth + 1))
stack.append((node.right, depth + 1))
return resultBoth versions are correct.
Key Takeaways
The maximum depth of a binary tree can be found using several traversal strategies.
With recursive DFS, the answer for each node is:
1 + the greater depth of its two subtreesWith BFS, the answer is the number of levels processed.
With iterative DFS, each node is stored together with its current depth.
All three approaches visit every node and therefore have a time complexity of:
O(n)The recursive DFS solution is generally the simplest and most natural solution for this problem.