Skip to content

137. Binary Tree Paths

šŸ”— LeetCode Problem: 257. Binary Tree Paths
šŸ“Š Difficulty: Easy
šŸ·ļø Topics: Binary Trees, Recursion, DFS, Backtracking, Path Construction

Problem Statement

Given the root of a binary tree, return all root-to-leaf paths in any order.

A leaf is a node with no children.

Example 1:

Input: root = [1,2,3,null,5]
Output: ["1->2->5","1->3"]

Example 2:

Input: root = [1]
Output: ["1"]

Constraints: - The number of nodes in the tree is in the range [1, 100] - -100 <= Node.val <= 100


🌳 Visual Understanding - Drawing All Paths!

Example 1: Multiple Paths

Tree: [1,2,3,null,5]

       1           ← Start here
      / \
     2   3         ← Two branches
      \
       5           ← One leaf here, one leaf at 3

All root-to-leaf paths:
  Path 1: 1 → 2 → 5  = "1->2->5" āœ“
  Path 2: 1 → 3      = "1->3"    āœ“

Visual representation:
       1
      / \
     2   3        Path to 3: [1,3]
      \
       5          Path to 5: [1,2,5]

Total: 2 paths
Output: ["1->2->5", "1->3"]

Example 2: Single Node

Tree: [1]

       1           ← This is both root AND leaf!

Only one path:
  Path 1: 1       = "1" āœ“

Output: ["1"]

Understanding "Root-to-Leaf":

MUST reach a leaf (both children null)!

Valid paths:
       1
      / \
     2   3        ← 2 and 3 are leaves āœ“

  Paths: "1->2", "1->3"

Invalid "paths":
       1          ← Can't stop here!
      / \
     2   3
    /
   4             ← Must continue to leaf (4)

  Only valid path: "1->2->4"
  Can't return "1" or "1->2" (not complete paths)

More Examples:

Example 3: Complete binary tree
       1
      / \
     2   3
    / \ / \
   4  5 6  7

All leaves: 4, 5, 6, 7
Paths:
  "1->2->4"
  "1->2->5"
  "1->3->6"
  "1->3->7"

Example 4: Left-skewed tree
    1
   /
  2
 /
3

Only one path:
  "1->2->3"

Example 5: All leaves at different levels
       1
      / \
     2   3
    /     \
   4       5
  /
 6

Paths:
  "1->2->4->6"  (depth 4)
  "1->3->5"     (depth 3)

🧠 The AHA Moment - Backtracking Pattern!

The Key Question:

"How do I collect ALL paths from root to leaves?"

Recursive + Backtracking Insight:

This problem requires BACKTRACKING!

Why?
  We need to:
  1. Build a path as we go down
  2. When we hit a leaf, save the path
  3. BACKTRACK to try other branches

Example:
       1
      / \
     2   3

Process:
  1. Start at 1, path = "1"
  2. Go to 2, path = "1->2"
  3. Hit leaf 2, save "1->2" āœ“
  4. BACKTRACK to 1, path = "1"
  5. Go to 3, path = "1->3"
  6. Hit leaf 3, save "1->3" āœ“
  7. Done!

Without backtracking, we'd lose track of where we are!

Visual Backtracking:

Tree:
       1
      / \
     2   3
      \
       5

DFS Traversal with path building:

Start: path = ""

Visit 1: path = "1"
  │
  ā”œā”€ Visit 2: path = "1->2"
  │  │
  │  ā”œā”€ Try left: null, skip
  │  │
  │  └─ Visit 5: path = "1->2->5"
  │     │
  │     ā”œā”€ Leaf! Save "1->2->5" āœ“
  │     │
  │     └─ BACKTRACK to 2: path = "1->2"
  │
  ā”œā”€ BACKTRACK to 1: path = "1"
  │
  └─ Visit 3: path = "1->3"
     │
     ā”œā”€ Leaf! Save "1->3" āœ“
     │
     └─ BACKTRACK to 1: path = "1"

Result: ["1->2->5", "1->3"]

🧠 Recursive Thinking - Building the Solution

Approach 1: Build String Path

We build the path as a String as we recurse

At each node:
  - Add current node to path
  - If leaf: add path to result
  - If not leaf: recurse with updated path

The path STRING is passed down
No need to explicitly backtrack (string is immutable in Java)

Base Case:

Base Case: Node is a leaf
  Add complete path to result list

Check:
  if (node.left == null && node.right == null) {
    result.add(currentPath);
    return;
  }

This is where we collect the path!

Recursive Case:

For non-leaf nodes:
  1. Add current node to path
  2. Recurse on left child (if exists)
  3. Recurse on right child (if exists)

Example:
  currentPath = "1->2"

  If left child exists:
    recurse with "1->2->5"

  If right child exists:
    recurse with "1->2->7"

šŸŽÆ Solution 1: Recursive DFS with String Building

Implementation:

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> result = new ArrayList<>();

        // Handle null root
        if (root == null) {
            return result;
        }

        // Start DFS with root value
        dfs(root, String.valueOf(root.val), result);

        return result;
    }

    private void dfs(TreeNode node, String path, List<String> result) {
        // Base Case: Leaf node - add complete path
        // WHY? Path must end at a leaf!
        if (node.left == null && node.right == null) {
            result.add(path);
            return;
        }

        // Recursive Case: Continue building path

        // Go left if possible
        if (node.left != null) {
            dfs(node.left, path + "->" + node.left.val, result);
        }

        // Go right if possible
        if (node.right != null) {
            dfs(node.right, path + "->" + node.right.val, result);
        }

        // Note: No explicit backtracking needed!
        // WHY? Strings are immutable - each recursive call
        // has its own copy of the path string
    }
}

Cleaner Version:

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> paths = new ArrayList<>();
        if (root == null) return paths;

        buildPaths(root, "", paths);
        return paths;
    }

    private void buildPaths(TreeNode node, String path, List<String> paths) {
        // Build current path
        path += node.val;

        // If leaf, save path
        if (node.left == null && node.right == null) {
            paths.add(path);
            return;
        }

        // Continue with "->" separator
        path += "->";

        // Recurse on children
        if (node.left != null) {
            buildPaths(node.left, path, paths);
        }
        if (node.right != null) {
            buildPaths(node.right, path, paths);
        }
    }
}

šŸŽÆ Solution 2: Recursive DFS with List Building (Explicit Backtracking)

Implementation:

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> result = new ArrayList<>();
        if (root == null) return result;

        List<Integer> path = new ArrayList<>();
        dfs(root, path, result);

        return result;
    }

    private void dfs(TreeNode node, List<Integer> path, List<String> result) {
        // Add current node to path
        path.add(node.val);

        // If leaf, convert path to string and save
        if (node.left == null && node.right == null) {
            result.add(pathToString(path));
        } else {
            // Continue exploring
            if (node.left != null) {
                dfs(node.left, path, result);
            }
            if (node.right != null) {
                dfs(node.right, path, result);
            }
        }

        // BACKTRACK: Remove current node
        // WHY? So parent can try other branches with clean path
        path.remove(path.size() - 1);
    }

    private String pathToString(List<Integer> path) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < path.size(); i++) {
            sb.append(path.get(i));
            if (i < path.size() - 1) {
                sb.append("->");
            }
        }
        return sb.toString();
    }
}

Why Explicit Backtracking?

With List (mutable):
  path.add(5);        // Modifies the list
  dfs(left, path);    // Passes modified list
  path.remove(...);   // Must restore! (backtrack)

Without backtracking:
  path = [1, 2, 5]
  After left recursion: still [1, 2, 5]
  Try right: adds to [1, 2, 5] → WRONG!

With backtracking:
  path = [1, 2, 5]
  After left recursion + backtrack: [1, 2]
  Try right: adds to [1, 2] → CORRECT!

With String (immutable):
  path = "1->2";
  dfs(left, path + "->5");  // Creates NEW string
  // Original path still "1->2" - no need to backtrack!

šŸ” Detailed Dry Run with Recursion Visualization

Tree:
       1
      / \
     2   3
      \
       5

Complete Recursion Flow (String Approach):

CALL STACK VISUALIZATION:

Level 0: binaryTreePaths(1)
│
ā”œā”€ result = []
ā”œā”€ Start: dfs(1, "1", result)
│
│  Level 1: dfs(1, "1", result)
│  │
│  ā”œā”€ node = 1, not a leaf (has children)
│  ā”œā”€ path = "1"
│  │
│  ā”œā”€ LEFT exists (2), recurse ─────────────┐
│  │                                         │
│  │  Level 2: dfs(2, "1->2", result)       │
│  │  │                                      │
│  │  ā”œā”€ node = 2, not a leaf (has right)   │
│  │  ā”œā”€ path = "1->2"                      │
│  │  │                                      │
│  │  ā”œā”€ LEFT is null, skip                 │
│  │  │                                      │
│  │  ā”œā”€ RIGHT exists (5), recurse ────────┐
│  │  │                                     │
│  │  │  Level 3: dfs(5, "1->2->5", result)│
│  │  │  │                                  │
│  │  │  ā”œā”€ node = 5, IS A LEAF! āœ“         │
│  │  │  ā”œā”€ path = "1->2->5"               │
│  │  │  ā”œā”€ Add to result: ["1->2->5"]     │
│  │  │  └─ return                          │
│  │  │                                     │
│  │  └─ [Returned from 5]                  │
│  │     No more children                   │
│  │                                         │
│  └─ [Returned from 2]                     │
│                                            │
│  ā”œā”€ RIGHT exists (3), recurse ────────────┐
│  │                                         │
│  │  Level 2: dfs(3, "1->3", result)       │
│  │  │                                      │
│  │  ā”œā”€ node = 3, IS A LEAF! āœ“            │
│  │  ā”œā”€ path = "1->3"                      │
│  │  ā”œā”€ Add to result: ["1->2->5","1->3"] │
│  │  └─ return                             │
│  │                                         │
│  └─ [Returned from 3]                     │
│                                            │
│  └─ All children processed                │
│                                            │
└─ return result: ["1->2->5", "1->3"]       │

FINAL RESULT: ["1->2->5", "1->3"]

Path Building Visualization:

Step-by-step path construction:

Step 1: Visit node 1
  path = "1"
  Not a leaf → continue

Step 2: Visit node 2 (left child of 1)
  path = "1" + "->" + "2" = "1->2"
  Not a leaf → continue

Step 3: Visit node 5 (right child of 2)
  path = "1->2" + "->" + "5" = "1->2->5"
  Is a leaf! → Save "1->2->5" āœ“

Step 4: Back to node 1 (string immutable, so still "1")

Step 5: Visit node 3 (right child of 1)
  path = "1" + "->" + "3" = "1->3"
  Is a leaf! → Save "1->3" āœ“

Result: ["1->2->5", "1->3"]

šŸ” Dry Run - List Approach with Backtracking

Tree:
       1
      / \
     2   3

With Explicit Backtracking:

Call Stack:

Level 0: dfs(1, [], result)
│
ā”œā”€ path.add(1) → path = [1]
ā”œā”€ Not a leaf
│
ā”œā”€ LEFT exists, recurse ──────────────────┐
│                                          │
│  Level 1: dfs(2, [1], result)           │
│  │                                       │
│  ā”œā”€ path.add(2) → path = [1, 2]        │
│  ā”œā”€ IS A LEAF! āœ“                       │
│  ā”œā”€ Convert to string: "1->2"          │
│  ā”œā”€ result = ["1->2"]                  │
│  ā”œā”€ BACKTRACK: path.remove(2)          │
│  │  path = [1]                          │
│  └─ return                              │
│                                          │
ā”œā”€ path = [1] (after backtrack!)         │
│                                          │
ā”œā”€ RIGHT exists, recurse ─────────────────┐
│                                          │
│  Level 1: dfs(3, [1], result)           │
│  │                                       │
│  ā”œā”€ path.add(3) → path = [1, 3]        │
│  ā”œā”€ IS A LEAF! āœ“                       │
│  ā”œā”€ Convert to string: "1->3"          │
│  ā”œā”€ result = ["1->2", "1->3"]          │
│  ā”œā”€ BACKTRACK: path.remove(3)          │
│  │  path = [1]                          │
│  └─ return                              │
│                                          │
ā”œā”€ BACKTRACK: path.remove(1)              │
│  path = []                               │
│                                          │
└─ return result: ["1->2", "1->3"]        │

Note: Backtracking ensures clean path for each branch!

šŸ“Š Complexity Analysis

Time Complexity: O(n)

Where n = number of nodes

Why?
  - Visit each node exactly once
  - At each node: O(1) operations (append to string)

But wait! String concatenation?
  - Each concatenation creates new string
  - For path of length k: O(k) per concatenation

Amortized analysis:
  - Total characters across all paths = O(n)
  - Average path length = O(log n) to O(n)

Overall: O(n) visits Ɨ O(k) string ops
  Best case: O(n log n) - balanced tree
  Worst case: O(n²) - skewed tree

For interviews: Say O(n) or O(n Ɨ height)

Space Complexity: O(n)

Space used:
  1. Recursion stack: O(h) where h = height
  2. Result list: O(n) - stores all paths
  3. Path strings: O(n) total characters

Best case (balanced): O(log n) + O(n) = O(n)
Worst case (skewed): O(n) + O(n) = O(n)

Overall: O(n)

Note: Result list dominates space complexity

āš ļø Common Mistakes

Mistake 1: Not Checking for Leaf

// āŒ WRONG - Adds incomplete paths!
private void dfs(TreeNode node, String path, List<String> result) {
    if (node == null) return;

    path += node.val;
    result.add(path);  // Adds EVERY node, not just leaves!

    // ...
}

// Tree:    1
//         / \
//        2   3
// Would add: "1", "1->2", "1->3"
// Should only add: "1->2", "1->3" (leaves!)

// āœ“ CORRECT - Check for leaf
if (node.left == null && node.right == null) {
    result.add(path);  // Only add at leaves!
}

Mistake 2: Forgetting to Backtrack (List Approach)

// āŒ WRONG - No backtracking!
private void dfs(TreeNode node, List<Integer> path, List<String> result) {
    path.add(node.val);

    if (node.left == null && node.right == null) {
        result.add(pathToString(path));
    }

    if (node.left != null) dfs(node.left, path, result);
    if (node.right != null) dfs(node.right, path, result);

    // Missing: path.remove(path.size() - 1);
}

// Result: Paths accumulate incorrectly!

// āœ“ CORRECT - Backtrack after exploring
path.remove(path.size() - 1);  // Clean up!

Mistake 3: Building String Incorrectly

// āŒ WRONG - Missing arrow between nodes
path = path + node.val;  // "123" instead of "1->2->3"

// āŒ WRONG - Extra arrow at start
path = "->" + node.val + path;  // "->1->2->3"

// āŒ WRONG - Extra arrow at end
if (node.left == null && node.right == null) {
    result.add(path + "->");  // "1->2->3->"
}

// āœ“ CORRECT - Arrow between nodes only
path = path + "->" + node.val;
// Or handle first node specially

Mistake 4: Modifying String Wrong

// āŒ WRONG - Trying to backtrack string
String path = "1->2";
path += "->5";
// How to remove "->5"? Can't easily!
path = path.substring(0, path.length() - 3);  // Messy!

// āœ“ BETTER - Use String (immutable, no backtrack needed)
dfs(node.left, path + "->" + node.left.val, result);

// āœ“ OR - Use StringBuilder with explicit backtracking
StringBuilder path = new StringBuilder();
path.append("->").append(node.val);
// ... explore ...
path.setLength(path.length() - 3);  // Backtrack

Mistake 5: Starting with Wrong Path

// āŒ WRONG - Starts with arrow
dfs(root, "", result);  // First call with empty string
// In dfs: path += "->" + node.val;
// Result: "->1->2->3"

// āœ“ CORRECT - Start with root value
dfs(root, String.valueOf(root.val), result);
// Then add arrows for children

Mistake 6: Not Handling Single Node

// āŒ WRONG - Doesn't handle single node tree
if (root == null) return result;
dfs(root, "", result);

// Tree: [1]
// If leaf check comes after building path,
// might return empty or malformed

// āœ“ CORRECT - Handle single node properly
if (root == null) return result;
dfs(root, String.valueOf(root.val), result);
// Leaf check works: "1" added correctly

šŸŽÆ Pattern Recognition - Path Collection

Core Pattern: Collect All Paths

Template for collecting all root-to-leaf paths:

void collectPaths(TreeNode node, 
                  PathType currentPath, 
                  List<PathType> result) {
    // Add current node to path
    updatePath(currentPath, node);

    // Base case: Leaf - save path
    if (isLeaf(node)) {
        result.add(copyOf(currentPath));
        return;
    }

    // Recursive case: Continue exploring
    if (node.left != null) {
        collectPaths(node.left, currentPath, result);
    }
    if (node.right != null) {
        collectPaths(node.right, currentPath, result);
    }

    // Backtrack if using mutable data structure
    if (isMutable) {
        backtrack(currentPath);
    }
}

1. Path Sum II (Problem 135 variation)

Collect all paths that sum to target
  - Same pattern!
  - Additional condition: sum equals target
  - Still collect at leaves

2. Sum Root to Leaf Numbers

Each path forms a number, sum all numbers
  - Similar path building
  - But convert to number instead of string
  - Return sum, not list

3. Binary Tree Maximum Path Sum

Find maximum sum path (can start/end anywhere)
  - More complex: not root-to-leaf
  - Uses depth calculation
  - Different pattern

4. All Paths From Source to Target (Graph)

Same concept but for graphs
  - DFS with backtracking
  - Collect all paths from source to target
  - Very similar structure!

When to Use This Pattern:

āœ“ "Find all paths" problems
āœ“ "Collect paths that satisfy X" problems
āœ“ Root-to-leaf enumeration
āœ“ When you need ALL solutions (not just one)
āœ“ Backtracking + DFS combination

šŸ“ Quick Revision Notes

šŸŽÆ Core Concept

Find all root-to-leaf paths as strings. Use DFS + backtracking. Build path as you recurse down. At leaf, save complete path. Two approaches: (1) String (immutable, no explicit backtrack), (2) List (mutable, must backtrack). Format: "node1->node2->node3". Must reach leaf (both children null) to add path.

⚔ Quick Implementation

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }

    public static TreeNode buildTree(Integer[] arr) {
        if (arr == null || arr.length == 0 || arr[0] == null) {
            return null;
        }

        TreeNode root = new TreeNode(arr[0]);
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        int i = 1;
        while (!queue.isEmpty() && i < arr.length) {
            TreeNode current = queue.poll();

            // Process left child
            if (i < arr.length) {
                if (arr[i] != null) {
                    current.left = new TreeNode(arr[i]);
                    queue.offer(current.left);
                }
                i++;
            }

            // Process right child
            if (i < arr.length) {
                if (arr[i] != null) {
                    current.right = new TreeNode(arr[i]);
                    queue.offer(current.right);
                }
                i++;
            }
        }

        return root;
    }

    public static void print(TreeNode root) {
        if (root == null) {
            System.out.println("[]");
            return;
        }

        List<String> result = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node != null) {
                result.add(String.valueOf(node.val));
                queue.offer(node.left);
                queue.offer(node.right);
            } else {
                result.add("null");
            }
        }

        while (!result.isEmpty() && result.get(result.size() - 1).equals("null")) {
            result.remove(result.size() - 1);
        }

        System.out.println(result);
    }    
}

class Solution {
  public List<String> binaryTreePaths(TreeNode root) {
    List<String> res = new ArrayList<>();
    if(root == null) {
      return res; 
    }

    // // Approach 1 - More natural using lists.
    // Create 1 list per 1 root-to-leaf path.
    // usingList(root, new ArrayList<>(), res);

    // Approach 2 - using strings.
    usingString(root, String.valueOf(root.val), res);

    return res;
  }

  private void usingString(TreeNode root, String currPath, List<String> res) {
    // same base case as earlier.
    if(root.left == null && root.right == null) {
      res.add(currPath);

      return;
    }

    // Same as earlier. Instead of adding list, directly modifying list.
    // Considering only when left part is there.
    if(root.left != null) {
      usingString(root.left, currPath + "->" + root.left.val, res);
    }    

    // Considering only when right part is there.
    if(root.right != null) {
      usingString(root.right, currPath + "->" + root.right.val, res);
    }    
  }

  private void usingList(TreeNode root, ArrayList<Integer> currPath, List<String> res) {
    // base case - reached leaf node. Add the leaf node to the path and build the result.
    // currPath holds root-to-leaf paths.
    if(root.left == null && root.right == null) {
      currPath.add(root.val);
      buildPath(currPath, res);

      return;
    }

    // Add the incoming node. For example, 1
    currPath.add(root.val);

    // Considering only when left part is there.
    if(root.left != null) {
      usingList(root.left, new ArrayList<>(currPath), res);
    }    

    // Considering only when right part is there.
    if(root.right != null) {
      usingList(root.right, new ArrayList<>(currPath), res);
    }

    // Remove the node before tracking back to the previous call.
    // Recusion concept: whatever got added should be removed before going back.
    currPath.remove(currPath.size() - 1);
  }

  private void buildPath(ArrayList<Integer> path, List<String> res) {
    StringBuilder sb = new StringBuilder();
    for(int i = 0; i < path.size(); i++) {
      sb.append(path.get(i));
      if(i != path.size() - 1) {
        sb.append("->");
      }
    }

    res.add(sb.toString());
  }

  public static void main(String[] args) {
    Solution s = new Solution();
    System.out.println(s.binaryTreePaths(TreeNode.buildTree(new Integer[] {1,2,3,null,5}))); //[1->2->5, 1->3]
  }
}

šŸ”‘ Key Insights

String vs List Approach:

String (immutable):
  + No explicit backtracking needed
  + Simpler code
  - New string created each time

List (mutable):
  + More efficient (no string copying)
  + Good for large trees
  - Must explicitly backtrack
  - More code

For interviews: Use String (simpler!)

Why Backtracking Matters:

Tree:
    1
   / \
  2   3

Without backtracking (List):
  path = [1, 2]
  After left: path = [1, 2]
  Try right: path becomes [1, 2, 3] āœ— WRONG!

With backtracking:
  path = [1, 2]
  After left + backtrack: path = [1]
  Try right: path becomes [1, 3] āœ“ CORRECT!

Backtracking restores state for next branch!

Leaf Requirement:

Same as Path Sum and Min Depth!

Only add path at LEAF:
  if (node.left == null && node.right == null)

Can't add at every node:
  Would include incomplete paths!

šŸŽŖ Memory Aid

"Build path down, save at leaf!"
"String: no backtrack needed!"
"List: backtrack or wrong path!"
"Arrow between nodes: ->!" ✨

āš ļø Don't Forget

  • Check for leaf (both children null)
  • Start with root value (not empty string)
  • Add arrows between nodes (not at start/end)
  • String approach simpler (no backtracking!)
  • List approach must backtrack (restore state!)
  • Convert list to string only at leaves

šŸ”— Backtracking Pattern

// Generic backtracking template

void backtrack(node, currentState, result) {
    // Add current to state
    state.add(node);

    // Success condition?
    if (isSuccess(state)) {
        result.add(copy(state));
    } else {
        // Continue exploring
        for (child : node.children) {
            backtrack(child, state, result);
        }
    }

    // BACKTRACK: restore state
    state.remove(node);
}

For this problem:
  state = path (string or list)
  success = leaf node
  children = left and right

šŸŽ‰ Congratulations!

You've mastered path collection with backtracking!

What You Learned:

āœ… Path collection - Finding all root-to-leaf paths
āœ… Backtracking concept - Build path, save, restore
āœ… String vs List - Immutable vs mutable approaches
āœ… Leaf requirement - Same pattern from previous problems
āœ… Path formatting - Building "1->2->3" format
āœ… DFS traversal - With state management

Pattern Evolution:

Problem 135: Path Sum
  - Find IF path exists with sum
  - Return: boolean
  - Stop when found

Problem 137: Binary Tree Paths
  - Find ALL paths
  - Return: List<String>
  - Collect all, no early stop

Key difference:
  135: Existence check (boolean)
  137: Collection (list)

Both require reaching LEAF! āœ“

The Backtracking Skill:

You now understand:
  āœ“ When to backtrack (mutable state)
  āœ“ When not needed (immutable state)
  āœ“ How to restore state
  āœ“ Why it's necessary

This applies to:
  - Path problems
  - Permutations
  - Combinations
  - Subsets
  - N-Queens
  - Sudoku solver

Fundamental algorithm skill! šŸ”‘

Next Steps:

Perfect problems to reinforce this: - Path Sum II (exact same pattern!) - Sum Root to Leaf Numbers (similar building) - All Paths Source to Target (graphs!)

You're building powerful problem-solving patterns! šŸŒ³šŸŽÆāœØ