Pattern #39

Tree Depth First Search (DFS)

Important interview questions, thinking patterns, call stack movement, state flow directions (Downward vs Upward), Preorder vs Postorder, and Go code templates.

Must Solve

18 core questions — solve these first.

  1. 1.Maximum Depth of Binary Tree
    easy
  2. 2.Same Tree
    easy
  3. 3.Invert Binary Tree
    easy
  4. 4.Symmetric Tree
    easy
  5. 5.Balanced Binary Tree
    easy
  6. 6.Diameter of Binary Tree
    easy
  7. 7.Path Sum
    easy
  8. 8.Path Sum II
    medium
  9. 9.Binary Tree Paths
    easy
  10. 10.Lowest Common Ancestor of a Binary Tree
    medium
  11. 11.Binary Tree Maximum Path Sum
    hard
  12. 12.Subtree of Another Tree
    easy
  13. 13.Count Good Nodes in Binary Tree
    medium
  14. 14.Sum Root to Leaf Numbers
    medium
  15. 15.House Robber III
    medium
  16. 16.Flatten Binary Tree to Linked List
    medium
  17. 17.Construct Binary Tree from Preorder and Inorder Traversal
    medium
  18. 18.Serialize and Deserialize Binary Tree
    hard

Also Important

10 more questions worth practicing.

  1. 19.Minimum Depth of Binary Tree
    easy
  2. 20.Sum of Left Leaves
    easy
  3. 21.Maximum Difference Between Node and Ancestor
    medium
  4. 22.Longest Univalue Path
    medium
  5. 23.Path Sum III
    medium
  6. 24.Delete Leaves With a Given Value
    medium
  7. 25.Find Leaves of Binary Tree
    medium
  8. 26.Binary Tree Cameras
    hard
  9. 27.Smallest Subtree with all Deepest Nodes
    medium
  10. 28.All Nodes Distance K in Binary Tree
    medium

How to Think

  1. Need explore every subtree?Tree DFS (Recursion / Call Stack)
  2. Need height / depth / diameter?DFS + return result UPWARD
  3. Need root-to-leaf path?DFS + carry state DOWNWARD
  4. Need children solved before parent?Postorder DFS (Left-Right-Root)
  5. Need root before children?Preorder DFS (Root-Left-Right)

Go Tree DFS Code Templates

Preorder & Postorder DFS Templates in Go

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

// 1. Preorder DFS: Root -> Left -> Right (Processing Root First)
func preorderDFS(root *TreeNode) {
    if root == nil {
        return
    }

    // Process current node first (e.g. serialize, copy, path build)
    fmt.Println(root.Val)

    preorderDFS(root.Left)
    preorderDFS(root.Right)
}

// 2. Postorder DFS: Left -> Right -> Root (Processing Children First)
func postorderDFS(root *TreeNode) int {
    if root == nil {
        return 0
    }

    leftHeight := postorderDFS(root.Left)
    rightHeight := postorderDFS(root.Right)

    // Process current node using children answers!
    return 1 + max(leftHeight, rightHeight)
}

👉 Total Time: O(N) | Space: O(H) recursion stack

State Flow Directions: Downward vs Upward

1. Downward State (Parameters)

Parent passes parameters down to children. Used in Path Sum II, Good Nodes, Max/Min Ancestor Diff.

2. Upward Result (Return Values)

Children calculate results and return them back up to parent. Used in Max Depth, Diameter, Max Path Sum, Balanced Tree.

Preorder vs Postorder Traversal Use Cases

Preorder (Root -> Left -> Right): Process current node before children. Best for Serialization, Tree Copying, Path Building, Flattening.

Postorder (Left -> Right -> Root): Process children before current node. Best for Height, Diameter, Balanced Tree, Subtree DP (House Robber III), Delete Leaves.

// Path Sum II (DFS + Backtracking Template)
func pathSum(root *TreeNode, targetSum int) [][]int {
    result := [][]int{}
    currentPath := []int{}

    var dfs func(node *TreeNode, remaining int)
    dfs = func(node *TreeNode, remaining int) {
        if node == nil {
            return
        }

        // 1. Append to current path
        currentPath = append(currentPath, node.Val)
        remaining -= node.Val

        // 2. Check if leaf node matching target
        if node.Left == nil && node.Right == nil && remaining == 0 {
            pathCopy := make([]int, len(currentPath))
            copy(pathCopy, currentPath)
            result = append(result, pathCopy)
        }

        // 3. Recurse subtrees
        dfs(node.Left, remaining)
        dfs(node.Right, remaining)

        // 4. Backtrack cleanup!
        currentPath = currentPath[:len(currentPath)-1]
    }

    dfs(root, targetSum)
    return result
}
Visual Memory Rule
DOWNWARD  → Carry state in function parameters (current path / remaining sum)
UPWARD    → Return result from recursion (height / max single branch gain)
Postorder → Children answer first (Height / Subtree DP)
Preorder  → Process root first (Path building / Serialization)

💡 Golden Rule: "Decide what information goes down to the children and what answer comes back up to the parent."

Common Interview Mistakes

1. Forgetting Nil Base Case

Always write if node == nil { return ... } at the start of your DFS function!

2. Not Defining DFS Meaning

Before writing code, explicitly state what dfs(node) returns (e.g. subtree height, max single branch path sum).

3. Recomputing Subtrees Repeatedly

Calling a separate DFS helper inside another DFS loop results in O(N²)! Return all required info in one single DFS traversal.

4. Forgetting Backtracking Cleanup

When carrying a shared mutable slice (path), remember to slice back path = path[:len(path)-1] after exploring subtrees!

Interview Rules

  1. 1. Subtree traversal / path problem? → Tree DFS
  2. 2. Children answers needed first?→ Postorder DFS (Left -> Right -> Root)
  3. 3. Root processed before children?→ Preorder DFS (Root -> Left -> Right)
  4. 4. State from parent to child? → Pass parameters DOWNWARD
  5. 5. Result from children to parent? → Send return values UPWARD
  6. 6. Root-to-leaf paths? → DFS + Backtracking (append -> recurse -> remove)
  7. 7. Negative child branch gain? → Ignore it using max(0, childGain)
  8. 8. Overall Complexity → Time: O(N) | Recursive Stack Space: O(H) (O(log N) balanced, O(N) skewed)

Small Rules

  1. Rule 1: Always handle nil base case first (if node == nil { return ... }).
  2. Rule 2: Clearly define what dfs(node) returns before writing code.
  3. Rule 3: Parameters carry state DOWNWARD; return values send results UPWARD.
  4. Rule 4: Postorder evaluates children first; Preorder evaluates root first.
  5. Rule 5: Backtracking on shared path slices requires popping the last element after subtrees return.

Production Thinking

Directory File System SearchRecursively traversing folder trees deep down one path first

DOM / UI Component TreesCollecting nested component elements & applying style transformations

Software Dependency TreesWalking package dependency subtrees (requires cycle check for graphs)

Organization Hierarchy SearchFinding all subordinates under a manager via subtree DFS

Remember This

Tree DFS        → Go deep
Base case       → nil
Preorder        → Root Left Right
Inorder         → Left Root Right
Postorder       → Left Right Root
Path info       → Pass DOWNWARD
Subtree info    → Return UPWARD
Height          → Postorder
Diameter        → Heights + Global
LCA             → Results come upward
Time            → O(N)
Space           → O(Height)

💡 Golden Rule: "Decide what information goes down to the children and what answer comes back up to the parent."