Hello! Welcome to our next lesson on binary trees.
In our last session, we explored how to determine key properties of a tree—like its maximum depth, diameter, and balanced status. We saw how a powerful post-order DFS traversal allows us to compute information from the bottom-up, passing results from the children to the parent.
Today, we'll flip that pattern. We will focus on solving path-finding problems, where we typically pass information down the tree from parent to child. Your learning outcome for this lesson is to solve problems involving finding a root-to-leaf path with a given sum. This is a fundamental tree problem category that frequently appears in technical interviews. We'll master it by tackling two classic variations: checking if such a path exists, and then finding all such paths.
1. Path Sum I: Does a Valid Path Exist?
Let's start with the foundational problem: given a binary tree and a targetSum, determine if there is a root-to-leaf path where adding up all the values along the path equals the targetSum.
A leaf node is a node with no children. A root-to-leaf path must start at the root and end at a leaf.

The most direct way to solve this is with Depth-First Search (DFS). The core idea is to traverse from the root downwards, keeping track of the sum needed to reach our target. At each node, we subtract its value from the sum we're looking for and pass this new, smaller target to its children.
Let's watch a video that explains this approach and its implementation.
Path Sum - Leetcode 112 - Python
The NeetCode video 'Path Sum - Leetcode 112 - Python' provides a clear, concise walkthrough of this recursive DFS strategy.
Watch the entire video (00:00 to 06:31). Pay close attention to these key points: The definition of a root-to-leaf path. The base case: what happens when you reach a null node? The leaf node condition: the sum check only happens at a leaf. The recursive step: how the results from the left and right subtrees are combined using or.
The Recursive Logic Deconstructed
To solidify the pattern, let's break down the logic of the recursive helper function, which we can call dfs(node, remaining_sum).
- Base Case (Termination): If
nodeisNone, it means we've gone past a leaf. This path is invalid, so we returnFalse. - Update State: We subtract the current node's value from the sum we need:
remaining_sum -= node.val. - Goal Check (Leaf Node): This is the most critical step. If the current node is a leaf (
node.left is None and node.right is None), we check if ourremaining_sumis now exactly0. If it is, we've found a valid path, and we returnTrue. - Recursive Step (Internal Node): If it's not a leaf, we continue the search. We need to find a valid path in either the left subtree OR the right subtree. We make two recursive calls:
dfs(node.left, remaining_sum)anddfs(node.right, remaining_sum). The logicalorcombines their results.
This approach elegantly explores all root-to-leaf paths. The recursive call stack naturally handles the "backtracking" for us.
For a more detailed written explanation and a discussion of common mistakes, the following resource is excellent.
112. Path Sum - In-Depth Explanation
Let's review the 'In-Depth Explanation' from Algo.monster. It provides a great breakdown of the intuition and common pitfalls.
Read the 'Intuition' section for a clear explanation of why DFS is a perfect fit. Then, carefully read the 'Common Pitfalls' section. Understanding these mistakes (like checking the sum at non-leaf nodes) is crucial for interviews.
2. Path Sum II: Finding All Valid Paths
Now for a common and slightly more complex variation. Instead of just returning True or False, we need to return a list of all root-to-leaf paths that sum up to the target.
This changes the problem significantly:
- We can't stop after finding the first valid path.
- We need to keep track of the actual nodes in the current path.
- This requires explicit backtracking.
The core idea is still DFS, but we'll add a list to our function's state to build the path as we go. The backtracking pattern is: Choose, Explore, Unchoose.
- Choose: When we visit a node, we add it to our current path list.
- Explore: We make recursive calls for its children.
- Unchoose: After the recursive calls for a node's children have returned, we must remove that node from our path list. This "unchoosing" is the backtracking step. It ensures that when we explore a sibling branch, the path is restored to its correct state.
The following video demonstrates this pattern.
Path Sum II - LeetCode 113 Python
The video 'Path Sum II - LeetCode 113 Python' by Deepti Talesra walks through this backtracking approach.
Watch from 00:59 to 07:56. Focus on how the list (path) and sumTotal (remaining sum) are managed during the recursion. Notice what happens when a valid leaf node is found and how the recursion continues.
The Backtracking Pattern in Detail
Let's dig into the two trickiest parts of this implementation, which are essential for any problem that involves collecting path results.
Leetcode 113. Path Sum II - by Kunal Sinha
This article on LeetCode 113 provides a great text-based explanation of the backtracking approach and its nuances.
Read the sections 'How do we approach this problem?', 'Approach: Backtracking', and especially 'Tricky bits'. This will clarify why you need backtracking and how to implement it correctly.
As the article highlights, there are two crucial details:
- Path Copying: When you find a valid path at a leaf node, you must add a copy of the current path to your results list (e.g.,
result.append(list(path))in Python). If you just appendedpathitself, all entries in yourresultlist would point to the same object, which you will continue to modify, leading to an incorrect final answer. - Backtracking with
pop(): The linepath.pop()(or its equivalent) after the recursive calls is the heart of backtracking. It cleans up the state, ensuring that the path is correct for explorations in other parts of the tree.

Here is a Python implementation template that follows this pattern:
def pathSum(root, targetSum):
result_paths = []
current_path = []
def dfs(node, remaining_sum):
if not node:
return
# 1. Choose: Add the current node to the path
current_path.append(node.val)
# 2. Goal Check: At a leaf, check if the path is a solution
if not node.left and not node.right and remaining_sum == node.val:
result_paths.append(list(current_path)) # Add a copy!
# 3. Explore: Recurse on children
dfs(node.left, remaining_sum - node.val)
dfs(node.right, remaining_sum - node.val)
# 4. Unchoose: Backtrack by removing the current node from the path
current_path.pop()
dfs(root, targetSum)
return result_paths
Test your understanding!
Consider the tree from the LINK image (Example 1) and a targetSum of 22. Trace the pathSumII algorithm.
When the dfs function is called on the node with value 7, what will the state of current_path and remaining_sum be just before the call?
Show answer
The call sequence to get to node 7 is 5 -> 4 -> 11 -> 7.
- Initial call:
dfs(node=5, remaining_sum=22) - Inside
dfs(5):current_pathbecomes[5]. It callsdfs(node=4, remaining_sum=17). - Inside
dfs(4):current_pathbecomes[5, 4]. It callsdfs(node=11, remaining_sum=13). - Inside
dfs(11):current_pathbecomes[5, 4, 11]. It will then calldfs(node=7, remaining_sum=2).
So, just before calling dfs on node 7, the state is:
current_path:[5, 4, 11]remaining_sum:2
Conclusion
Excellent work! You've just learned one of the most fundamental patterns for solving tree problems. By mastering these two "Path Sum" variations, you've equipped yourself with a powerful template that applies to many other interview questions.
Key Takeaways:
- DFS for Paths: DFS is the go-to algorithm for path-finding problems in trees, as it naturally explores paths from root to leaf.
- Passing State Down: Unlike the property problems from our last lesson, path-finding often involves passing state (like a
remaining_sum) down the recursion. - Path Sum I (Existence): Use a simple recursive DFS. A leaf node check is critical, and results from subtrees are combined with a logical
or. - Path Sum II (Collecting): This requires the full backtracking pattern (Choose -> Explore -> Unchoose). Remember to manage a
current_pathlist,pop()to backtrack, and add a copy of the path to your results.
In our next lesson, we will tackle another classic problem that builds on tree traversals: serializing a binary tree to a string and deserializing it back. This is a common design-style question in interviews and a great test of your traversal knowledge.