Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 28

Tree Traversal and Properties

Recommended resources

Hello! Welcome back.

In our last two lessons, we built a solid foundation in binary tree traversals, covering both the classic recursive DFS methods and their iterative counterparts using a stack. You now have the core tools for exploring any binary tree.

Today, we'll apply these tools to solve three classic and high-frequency interview problems. Your learning outcome is to solve problems involving tree properties like maximum depth, diameter, and balanced status. We'll see how a single, elegant pattern—a modified Depth-First Search—can efficiently solve all of them. This lesson is all about moving from traversal theory to practical, high-ROI problem-solving.

1. Maximum Depth of a Binary Tree

The "maximum depth" (or height) of a binary tree is the length of the longest path from the root node down to the farthest leaf node. For this problem, we'll follow the common convention of counting the number of nodes on that path.

The Recursive DFS Approach

The most intuitive way to solve this is recursively. Think about it: the maximum depth of a tree is simply 1 (for the root itself) plus the maximum depth of its largest subtree (either left or right).

This gives us a very simple recursive relation:
maxDepth(node) = 1 + max(maxDepth(node.left), maxDepth(node.right))

The base case is a null node, which has a depth of 0.

This recursive breakdown is a fundamental pattern in tree algorithms. The following video provides a clear explanation and implementation of this approach, along with two iterative alternatives.

Maximum Depth of Binary Tree - 3 Solutions - Leetcode 104 - Python

Let's start with the video 'Maximum Depth of Binary Tree' from NeetCode. We'll focus first on the recursive DFS solution, which is the most common and elegant.

Watch from the beginning (00:00) to 05:17. Pay close attention to the definition of max depth and how the recursive formula is derived and implemented. This post-order traversal pattern (processing children before the parent) is key.

This image provides a great visualization of how the depth is calculated and passed up the call stack from the leaves to the root.

Calculating Maximum Depth of a Binary Tree
This diagram illustrates the recursive, bottom-up calculation of maximum depth. For each node, its depth is calculated as `1 + max(left_subtree_depth, right_subtree_depth)`.

Alternative: The Iterative BFS Approach

Another intuitive way to find the maximum depth is to think of it as finding the total number of levels in the tree. Breadth-First Search (BFS) is perfect for this, as it explores the tree level by level. We can perform a standard level-order traversal and simply count how many levels we process.

Maximum Depth of Binary Tree - 3 Solutions - Leetcode 104 - Python

Now, watch the next section of the same NeetCode video, which explains the BFS (level-order traversal) approach. This is a great way to reinforce your understanding of BFS.

Continue watching from 05:17 to 10:33. Notice how a queue is used and how the number of levels is tracked. This is a very common pattern for problems involving tree levels.

While the recursive DFS is often the most concise code, the iterative BFS approach is also highly effective and avoids recursion depth limits, making it a robust solution. The video also covers an iterative DFS approach, which is good for completeness but less common in interviews for this specific problem.


2. Diameter of a Binary Tree

This is a slightly trickier, but extremely common, follow-up problem. The diameter of a binary tree is the length of the longest path between any two nodes. A critical point is that this path may or may not pass through the root. The length is measured by the number of edges.

The key insight is that for any given node, the longest path that passes through it is the sum of the maximum depth of its left subtree and the maximum depth of its right subtree. The tree's overall diameter is the maximum of these path lengths calculated for every node.

A naive O(N²) approach would be to iterate through every node, and for each one, calculate the depths of its left and right subtrees. We can do much better.

The Efficient O(N) Approach

The optimal solution involves a single post-order DFS traversal. We can write a helper function that does two things simultaneously:

  1. Returns the maximum depth of the subtree rooted at the current node.
  2. Updates a global or non-local variable to track the maximum diameter found so far. The diameter at the current node is left_depth + right_depth.

This "return one thing, update another" pattern is incredibly powerful.

543. Diameter of Binary Tree - In-Depth Explanation

To understand this pattern, let's read the 'Intuition' and 'Solution Approach' sections from the Algo.monster article '543. Diameter of Binary Tree'. It breaks down the core idea very clearly.

Read the 'Intuition' section to grasp why we combine height calculation and diameter updates. Then, read the 'Solution Approach' which details the DFS helper function's dual purpose.

Now, let's see this in action. The following video implements this exact logic.

Diameter of a Binary Tree - Leetcode 543 - Python

The NeetCode video 'Diameter of a Binary Tree' demonstrates how to implement this efficient O(N) solution. Pay close attention to how a single recursive function achieves both goals.

Watch from 05:00 to 09:49 for the conceptual explanation of the bottom-up approach. Then, watch the code implementation from 12:26 to 15:34. Notice how the function returns the height (1 + max(left, right)), but updates the diameter using left + right.

Common Pitfall: A frequent mistake is confusing node count with edge count. For this problem, the diameter is the number of edges. If your depth/height function returns the number of nodes, the diameter through a node would be left_depth + right_depth. If your depth/height function returns the number of edges (i.e., height), the diameter is left_height + right_height. The NeetCode video is slightly different, using 2 + left + right because his height function for a null tree returns -1, which is a common trick to make the math work out. The core idea is the same: sum the path lengths down the left and right.


3. Balanced Binary Tree

A binary tree is height-balanced if, for every node in the tree, the absolute difference between the height of its left and right subtrees is no more than 1.

Balanced Binary Tree Explanation
This image shows a balanced tree (left, green check) where the height difference at every node is ≤ 1, and an unbalanced tree (right, red 'X') where node 'C' violates this rule with a height difference of 2.

Just like with the diameter problem, we can devise an efficient O(N) solution using a single post-order traversal that avoids redundant height calculations.

The O(N) "Sentinel Value" Approach

The strategy is to create a DFS helper function that checks for balance as it calculates height.

  • If a subtree is balanced, the function returns its height (a non-negative integer).
  • If a subtree is unbalanced, the function immediately returns a special "sentinel" value, like -1, to signal this.

When a recursive call receives a -1 from one of its children, it knows the tree is unbalanced and can immediately stop and propagate the -1 up the call stack, enabling an early exit.

110. Balanced Binary Tree - In-Depth Explanation

The Algo.monster article '110. Balanced Binary Tree' provides an excellent explanation of this sentinel-based approach. It's a very clean and efficient pattern.

First, read the 'Problem Description' to solidify the definition. Then, focus on the 'Intuition' and 'Solution Approach' sections to understand how the -1 sentinel value works. Finally, follow the 'Example Walkthrough' to trace the algorithm on a concrete example.

The core logic within the recursive helper function looks like this in Python:

def check_height(node):
    if not node:
        return 0  # Height of an empty tree

    # Recursively check left and right subtrees
    left_height = check_height(node.left)
    if left_height == -1:
        return -1  # Left subtree is unbalanced

    right_height = check_height(node.right)
    if right_height == -1:
        return -1  # Right subtree is unbalanced

    # Check the current node's balance
    if abs(left_height - right_height) > 1:
        return -1  # This node is unbalanced

    # If all is well, return the height of this subtree
    return 1 + max(left_height, right_height)
Test your understanding!

Consider a "perfect" binary tree of height 2 (3 levels of nodes, 7 nodes total).

  1. What is its maximum depth (counting nodes)?
  2. What is its diameter (counting edges)?
  3. Is it height-balanced?
      A
     / \
    B   C
   / \ / \
  D  E F  G
Show answer
  1. Maximum Depth: The longest path from root to leaf (e.g., A -> B -> D) has 3 nodes. So, the maximum depth is 3.
  2. Diameter: The longest path between any two nodes is from one leaf to another on the opposite side (e.g., D -> B -> A -> C -> G). This path has 4 edges. So, the diameter is 4.
  3. Is it balanced? Yes.
    • At leaves (D, E, F, G), |0 - 0| = 0.
    • At B and C, |1 - 1| = 0.
    • At the root A, the height of the left subtree is 2 and the height of the right subtree is 2. The difference is |2 - 2| = 0.
      Since the condition holds for all nodes, the tree is height-balanced.

Conclusion

Great job! You've just tackled three fundamental binary tree problems that are cornerstones of technical interviews. The real takeaway isn't just the solution to these three problems, but the underlying pattern.

Key Takeaways:

  • Post-order Traversal is Powerful: Many tree property problems can be solved by computing information from child nodes first and then making a decision at the parent node.
  • Dual-Purpose DFS: An efficient pattern is to use a single recursive DFS function that both returns a primary value (like height) and updates a secondary value (like diameter or a boolean flag) via a non-local/global variable or a sentinel return value.
  • Complexity: All three problems can be solved optimally with O(N) time complexity, as we visit each node once, and O(H) space complexity for the recursion stack (where H is the tree's height).

In our next lesson, we'll continue using DFS to tackle another common category of tree problems: path-finding, such as finding a root-to-leaf path that sums to a specific value.