Chapter 8: Tree Traversal (BFS & DFS)

Chapter 8: Tree Traversal (BFS & DFS) ​

Concept & When to Use ​

Tree traversal is a fundamental technique in binary trees and graphs, used to explore nodes in a structured manner. There are two primary traversal methods:

  1. Breadth-First Search (BFS) – Explores all nodes at the same depth before moving deeper.
  2. Depth-First Search (DFS) – Explores as deep as possible before backtracking.

These approaches help solve problems related to tree structure, hierarchy, and relationships efficiently.

When to Use BFS vs. DFS ​

CriteriaBFS (Level Order Traversal)DFS (Preorder, Inorder, Postorder)
When to use?Finding shortest path, level-wise traversalFinding ancestors, validating BST, path-finding
Space ComplexityO(N) (queue holds all nodes at a level)O(H) (stack holds recursion depth, H=height)
Best forProblems needing level-wise relationshipsProblems requiring full tree exploration
Iterative Implementation?Uses a queue (FIFO)Uses a stack (LIFO) or recursion

Grind 75 Problems ​

The Tree Traversal pattern is crucial for solving the following Grind 75 problems:

  1. Binary Tree Level Order Traversal (BFS)
  2. Lowest Common Ancestor (DFS)
  3. Validate Binary Search Tree (DFS)

We explore different solutions and trade-offs for these problems.

Solutions & Trade-offs ​

1. Binary Tree Level Order Traversal (BFS) ​

💡 Problem: Given a binary tree, return its level-order traversal (left to right, level by level).

Approach: BFS (Queue) – O(N) Time, O(N) Space ​

  • Use a queue (FIFO) to process nodes level by level.
  • Maintain a list of nodes for each level.

Python Implementation (BFS) ​

python
from collections import deque

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

def levelOrder(root: TreeNode) -> list[list[int]]:
    if not root:
        return []

    result, queue = [], deque([root])

    while queue:
        level = []
        for _ in range(len(queue)):  # Process all nodes at the current level
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(level)

    return result

✅ Trade-offs:

  • O(N) time complexity (each node is visited once).
  • O(N) space complexity (stores all nodes at the deepest level).
  • BFS ensures level-wise traversal, but uses more memory than DFS.

2. Lowest Common Ancestor (DFS) ​

💡 Problem: Given a binary tree and two nodes p and q, find their Lowest Common Ancestor (LCA).

Approach: DFS (Recursive) – O(N) Time, O(H) Space ​

  • Traverse the tree using DFS until p or q is found.
  • If a node is an ancestor of both p and q, return it as the LCA.

Python Implementation (DFS) ​

python
def lowestCommonAncestor(root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
    if not root or root == p or root == q:
        return root  # Found p or q, return current node

    left = lowestCommonAncestor(root.left, p, q)
    right = lowestCommonAncestor(root.right, p, q)

    if left and right:
        return root  # This is the LCA

    return left if left else right  # Return non-null subtree

✅ Trade-offs:

  • O(N) time complexity (DFS visits each node once).
  • O(H) space complexity (recursive stack depth is the tree height).
  • Recursive DFS is elegant, but may cause stack overflow in deep trees.

3. Validate Binary Search Tree (DFS) ​

💡 Problem: Given a binary tree, determine if it is a valid Binary Search Tree (BST).

Approach: DFS (Inorder Traversal) – O(N) Time, O(H) Space ​

  • Perform an inorder traversal (left → root → right).
  • Ensure values are strictly increasing.

Python Implementation (DFS) ​

python
def isValidBST(root: TreeNode) -> bool:
    def inorder(node, lower=float('-inf'), upper=float('inf')):
        if not node:
            return True

        if node.val <= lower or node.val >= upper:
            return False  # Violates BST property

        return inorder(node.left, lower, node.val) and inorder(node.right, node.val, upper)

    return inorder(root)

✅ Trade-offs:

  • O(N) time complexity (visits each node once).
  • O(H) space complexity (recursive depth depends on tree height).
  • DFS is memory-efficient for balanced trees but can cause stack overflows in skewed trees.

BFS vs. DFS: Which One to Use? ​

ScenarioUse BFS (Queue)Use DFS (Stack/Recursion)
Level-wise traversal required?✅ Yes❌ No
Searching for shortest path?✅ Yes (e.g., unweighted graphs)❌ No
Tree structure validation (BST)?❌ No✅ Yes (inorder traversal)
Tree depth-related problems?❌ No✅ Yes (finding ancestors, recursion)
Memory constraints?❌ More memory✅ Less memory in balanced trees

Key Takeaways ​

✅ BFS (Queue) is best for level-wise traversal and shortest paths.

✅ DFS (Recursion/Stack) is efficient for ancestor, validation, and search problems.

✅ Iterative solutions avoid recursion depth limits but may be harder to implement.

✅ DFS (Inorder) is ideal for checking BST validity.

Mastering BFS & DFS will help you efficiently traverse trees and solve search, validation, and relationship-based problems! 🚀