← Back to DevBytes

Solving Maximum Depth of Binary Tree in Python: Step-by-Step Guide

Solving Maximum Depth of Binary Tree in Python: Step-by-Step Guide

The Maximum Depth of Binary Tree problem is one of the most fundamental challenges you will encounter when learning tree data structures and recursion. It asks a simple question: what is the longest path from the root node down to the farthest leaf node? Despite its apparent simplicity, this problem teaches essential concepts such as recursive traversal, base cases, and iterative alternatives using stacks or queues. In this tutorial, we will explore the problem in depth, build a working solution from scratch, and discuss best practices that will help you write cleaner, more efficient tree algorithms in Python.

What Is the Maximum Depth of a Binary Tree?

A binary tree is a hierarchical data structure in which every node has at most two children, typically referred to as the left and right child. The depth (or height) of a binary tree is defined as the number of nodes along the longest path from the root node down to the deepest leaf node. A leaf node is a node that has no children.

For example, consider the following binary tree:

        3
       / \
      9  20
         / \
        15  7

The longest path is 3 -> 20 -> 15 or 3 -> 20 -> 7, both of which contain 3 nodes. Therefore, the maximum depth of this tree is 3.

It is important to note the convention regarding empty trees. An empty tree (where the root is None) has a depth of 0. This convention forms the base case for most recursive solutions.

Why This Problem Matters

The Maximum Depth problem is not just an academic exercise. It appears frequently in coding interviews because it tests several core competencies at once:

Beyond interviews, calculating tree depth is a building block for more advanced algorithms such as balanced tree checks, subtree comparisons, and path-sum problems.

Defining the Tree Node

Before we can solve the problem, we need a way to represent a binary tree in Python. The most common approach is to define a simple TreeNode class:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

Each node stores a value and references to its left and right children. If a child does not exist, the reference is None. This minimal structure is sufficient for solving the problem and is the standard representation used on platforms like LeetCode.

Approach 1: Recursive Depth-First Search

The most intuitive solution uses recursion. The key insight is that the maximum depth of a tree rooted at a given node is 1 (for the node itself) plus the maximum depth of its left and right subtrees. If the node is None, the depth is 0.

Here is the recursive implementation:

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        # Base case: an empty tree has depth 0
        if root is None:
            return 0
        
        # Recursively compute the depth of left and right subtrees
        left_depth = self.maxDepth(root.left)
        right_depth = self.maxDepth(root.right)
        
        # The depth of the current node is 1 plus the larger subtree depth
        return 1 + max(left_depth, right_depth)

Let us trace through the example tree above. Starting at the root node 3, we recursively compute the depth of the left subtree rooted at 9 and the right subtree rooted at 20. The node 9 has no children, so its depth is 1. The node 20 has two children, 15 and 7, each with depth 1, so the depth of the subtree rooted at 20 is 2. Finally, the depth of the root is 1 + max(1, 2) = 3.

Complexity Analysis

Approach 2: Iterative Depth-First Search Using a Stack

Recursion is elegant, but it can cause stack overflow errors for very deep trees. An iterative approach using an explicit stack avoids this issue. The idea is to simulate the recursive calls by pushing nodes onto a stack along with their current depth.

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if root is None:
            return 0
        
        stack = [(root, 1)]
        max_depth = 0
        
        while stack:
            node, depth = stack.pop()
            max_depth = max(max_depth, depth)
            
            if node.left:
                stack.append((node.left, depth + 1))
            if node.right:
                stack.append((node.right, depth + 1))
        
        return max_depth

In this version, we initialize the stack with the root node and a depth of 1. We then repeatedly pop a node from the stack, update the maximum depth observed so far, and push the children with an incremented depth. Because we use a stack, this is effectively a depth-first traversal.

The time and space complexity remain O(n) and O(h) respectively, but we have replaced the implicit call stack with an explicit data structure that we control.

Approach 3: Iterative Breadth-First Search Using a Queue

Another elegant iterative solution uses breadth-first search. The idea is to process the tree level by level, counting how many levels we encounter. Each level corresponds to one unit of depth.

from collections import deque

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if root is None:
            return 0
        
        queue = deque([root])
        depth = 0
        
        while queue:
            level_size = len(queue)
            depth += 1
            
            for _ in range(level_size):
                node = queue.popleft()
                if node.left:
                    queue.append(node.left)
                if node.right:
                    queue.append(node.right)
        
        return depth

Here, we use a deque from the collections module for efficient O(1) pops from the front. For each level, we record the number of nodes in the queue, increment the depth counter, and process all nodes at that level by removing them and adding their children. When the queue is empty, the depth counter holds the maximum depth.

This approach has the same O(n) time complexity, but its space complexity is O(w), where w is the maximum width of the tree. For a balanced tree, the widest level can contain up to n/2 nodes, so the space complexity is O(n) in the worst case.

Testing the Solutions

To verify that our implementations work correctly, let us build the example tree and run each solution:

# Build the example tree:
#        3
#       / \
#      9  20
#         / \
#        15  7

root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

solution = Solution()

print("Recursive DFS:", solution.maxDepth(root))  # Output: 3

# Test edge cases
print("Empty tree:", solution.maxDepth(None))      # Output: 0

single_node = TreeNode(42)
print("Single node:", solution.maxDepth(single_node))  # Output: 1

# Skewed tree: 1 -> 2 -> 3
skewed = TreeNode(1, TreeNode(2, TreeNode(3)))
print("Skewed tree:", solution.maxDepth(skewed))   # Output: 3

All three approaches should produce the same results for these test cases. Testing edge cases such as empty trees, single-node trees, and skewed trees is essential to ensure the robustness of your solution.

Best Practices

When solving tree problems like this one, keep the following best practices in mind:

Common Pitfalls

Even experienced developers can make mistakes with this problem. Here are a few pitfalls to watch out for:

Extending the Solution

Once you are comfortable with the maximum depth problem, you can extend your knowledge to related challenges:

Each of these problems builds on the same foundational skills you practiced here, so mastering the maximum depth problem will pay dividends across many tree-related challenges.

Conclusion

The Maximum Depth of Binary Tree problem is a deceptively simple exercise that opens the door to a deeper understanding of recursion, tree traversal, and algorithmic complexity. By working through recursive, stack-based, and queue-based solutions, you gain a versatile toolkit that applies to countless other tree problems. Remember to always handle the base case, test edge cases thoroughly, and choose the right approach based on the constraints of your input. With these techniques in hand, you are well-equipped to tackle not only this problem but the broader family of tree algorithms that build upon it.

— Ad —

Google AdSense will appear here after approval

← Back to all articles