CMD Guide
HomeDSATrees

Introduction to Root to Leaf Path Pattern

A root-to-leaf path problem accumulates state (a sum, a list of values, a boolean condition) while a DFS walks downward from the root, and finalizes/compares that state exactly when the recursion hits a node with no children — because only at a leaf is the "path" complete and meaningful to score against every other path.

Recognize the pattern

Brute force vs optimal

Brute force: without any stored path or parent information, a bare root+leaf reference does not tell you which edges connect them. Recovering the path requires an O(n) DFS search from the root for every leaf (walking and backtracking until that leaf is matched), so recomputing all leaf sums this way costs O(n) leaves × O(n) search = O(n²) in the worst case. A slightly better brute force precomputes a parent-pointer map in one O(n) pass, then walks upward from each leaf to the root in O(h) — but that still pays O(n) upfront for the map, and walking "up from a leaf" only works if you can uniquely identify which node is the leaf in the first place (a bare value is not a unique identifier when duplicates exist). Either version does real extra work that the single-pass approach below needs zero of.

Optimal: one DFS pass that carries currentSum downward as a parameter (or return value). Each node is visited exactly once and does O(1) work, so the whole traversal is O(n) time — no path is ever reconstructed or re-walked.

Complexity, derived

Time: the recursion tree has exactly one call per tree node (n nodes), and each call does O(1) work (add value, check leaf, compare max) before making at most 2 recursive calls. Total work = n × O(1) = O(n) — strictly better than the O(n²) (or O(n) extra setup + O(n·h)) brute force above.

Space: no extra data structure holds all paths — only the call stack. Stack depth equals the current path length, bounded by the tree height h. So space is O(h): O(log n) for a balanced tree, O(n) worst case for a skewed (linked-list-shaped) tree.

Worked example

Tree: root=8, left=4 (leaf), right=9 with left child 1 and right child 6 (both leaves). Values: 8 → {4, 9 → {1, 6}}.

In the table below, each call receives currentSum as the sum accumulated before visiting the current node (the "incoming" value); the code immediately adds that node's own value to it before checking leaf status. The "sum after adding" column is what the code holds once that addition happens, and it's this post-add value that gets compared against maxSum at a leaf.

CallIncoming currentSumSum after adding nodeLeaf?maxSum after
findMaxSum(8, 0)00+8=8noMIN
findMaxSum(4, 8)88+4=12yes12
findMaxSum(9, 8)88+9=17no12
findMaxSum(1, 17)1717+1=18yes18
findMaxSum(6, 17)1717+6=23yes23

Three root-to-leaf paths exist: 8→4 (sum 12), 8→9→1 (sum 18), and 8→9→6 (sum 23). The last call to reach a leaf, findMaxSum(6, 17), produces the largest sum, so the traversal finishes with maxSum = 23 along the path 8→9→6.

Java

class TreeNode {
    int val; TreeNode left, right;
    TreeNode(int v) { val = v; }
}

class Solution {
    private int maxSum = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        findMaxSum(root, 0);
        return maxSum;
    }

    private void findMaxSum(TreeNode node, int currentSum) {
        if (node == null) return;
        currentSum += node.val;
        if (node.left == null && node.right == null) {
            maxSum = Math.max(maxSum, currentSum);
            return;
        }
        findMaxSum(node.left, currentSum);
        findMaxSum(node.right, currentSum);
    }
}

Go

import "math"

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

func maxPathSum(root *TreeNode) int {
    maxSum := math.MinInt64

    var findMaxSum func(node *TreeNode, currentSum int)
    findMaxSum = func(node *TreeNode, currentSum int) {
        if node == nil {
            return
        }
        currentSum += node.Val
        if node.Left == nil && node.Right == nil {
            if currentSum > maxSum {
                maxSum = currentSum
            }
            return
        }
        findMaxSum(node.Left, currentSum)
        findMaxSum(node.Right, currentSum)
    }

    findMaxSum(root, 0)
    return maxSum
}

Same shape as the Java version: currentSum is passed by value into the closure, so each recursive branch gets its own independent copy — sibling subtrees can never see each other's accumulated sum.

Pitfalls

When to use / when not

Use root-to-leaf DFS when the problem's definition of "path" is explicitly anchored at the root and terminates at a leaf (path sum, root-to-leaf number concatenation, path-exists-with-target-sum). Carry state downward as parameters — no extra memory beyond the stack.

Vs. BFS level-order: BFS can also enumerate root-to-leaf paths (tracking a partial path per queue entry) but costs O(n) extra space for the queue plus O(n) or more for stored partial paths, with no benefit unless you need shortest-path-by-level or level-synchronized processing — DFS is simpler and uses less space (O(h) vs O(n)) for pure path aggregation.

Vs. general tree DP (any-node-to-any-node paths): if the path doesn't have to start at the root, you need a bottom-up recurrence returning the best downward extension per subtree, not a top-down accumulator — don't reach for root-to-leaf DFS there.

Vs. "count any downward path summing to K" (Path Sum III, LeetCode 437): the path may start and end at any node as long as it goes strictly downward, so leaf-anchored accumulation is the wrong tool. The efficient answer carries a running prefix-sum from the root plus a hashmap of prefix-sum → count seen so far on the current root path; at each node the number of valid paths ending here is map[currentPrefix − K]. Crucially you must decrement that map entry as you unwind (the same backtracking discipline as the path-list case) so sums from one branch never leak into a sibling. This turns an O(n²) "try every start node" brute force into O(n).

Takeaways

Recall: Why is a node with only a right child not treated as a leaf in this pattern, and what bug results if you check leaf-ness by node.right == null alone?


Derived from classic root-to-leaf DFS treatments (e.g., LeetCode 112/113/129 path-sum family) and standard DFS complexity analysis for binary trees.

🤖 Don't fully get this? Learn it with Claude

Stuck on Introduction to Root to Leaf Path Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Introduction to Root to Leaf Path Pattern** (DSA) and want to truly understand it. Explain Introduction to Root to Leaf Path Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Introduction to Root to Leaf Path Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Introduction to Root to Leaf Path Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Introduction to Root to Leaf Path Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes