Path Sum III

Medium DFS

Problem

Given the root of a binary tree and an integer targetSum, return the number of paths where the sum of the values along the path equals targetSum. The path does not need to start or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes).

  • The number of nodes in the tree is in the range [0, 1000]
  • −10⁹ ≤ Node.val ≤ 10⁹
  • −1000 ≤ targetSum ≤ 1000

Example

Input: root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
Output: 3

The paths that sum to 8 are: 1. 5 -> 3 2. 5 -> 2 -> 1 3. -3 -> 11 The algorithm uses a prefix sum map to track cumulative sums from the root to the current node. At each node, it calculates the current cumulative sum and checks if there exists a prefix sum such that current_sum - prefix_sum = targetSum. This indicates a valid path ending at the current node. The prefix map is updated before exploring children and restored after to maintain correct counts for sibling branches.

Approach

Straightforward Solution

A naive approach would explore all paths starting from every node, resulting in O(n^2) time complexity, which is inefficient for large trees.

Core Observation

The problem reduces to finding the number of sub-paths in a root-to-current-node path whose sums equal targetSum. Using prefix sums, the difference between two prefix sums equals the sum of the nodes in between, enabling O(1) checks for valid paths ending at the current node.

Path to Optimal

The key insight is to use a prefix sum map that records how many times a particular cumulative sum has occurred along the current path. At each node, the algorithm calculates the current cumulative sum and checks if (current_sum - targetSum) exists in the map, indicating a valid path. This transforms the problem into a DFS with prefix sum bookkeeping, reducing redundant computations.

Optimal Approach

Perform a DFS traversal, maintaining a prefix sum map that counts occurrences of cumulative sums along the current path. For each node, update the current sum, check for valid paths using the map, recurse into children, and then backtrack by decrementing the count of the current sum. This ensures correct counting of paths and avoids double counting across different branches.

Time

O(n)

Each node is visited once in the DFS traversal. Prefix sum map operations (get, set) are O(1) on average, resulting in overall linear time.

Space

O(n)

The prefix sum map stores counts of cumulative sums along the current path, which can be up to the height of the tree (O(n) in worst case). The recursion stack also uses O(n) space in the worst case.

Pattern Spotlight

DFS (Prefix Sum with Backtracking)

When counting paths with a target sum in a tree, use prefix sums to convert the problem into counting subarrays with a given sum along root-to-node paths, and carefully backtrack prefix counts to maintain correctness across branches.

Solution

1class Solution:
2 def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
3 prefix = {0: 1}
4
5 def dfs(node, curr_sum):
6 if not node:
7 return 0
8
9 curr_sum += node.val
10 count = prefix.get(curr_sum - targetSum, 0)
11
12 prefix[curr_sum] = prefix.get(curr_sum, 0) + 1
13
14 count += dfs(node.left, curr_sum)
15 count += dfs(node.right, curr_sum)
16
17 prefix[curr_sum] -= 1
18
19 return count
20
21 return dfs(root, 0)

Step-by-Step Solution

1

Initialize Prefix Sum Map to Track Cumulative Sums

3prefix = {0: 1}

Objective

To create a dictionary that maps cumulative sums to their frequency along the current traversal path.

Key Insight

Starting with prefix sum 0 occurring once accounts for paths that start at the root and sum exactly to targetSum. This initialization is critical to correctly count paths that begin at the root node without requiring special case handling.

Interview Quick-Check

Core Logic

The prefix map stores cumulative sums and their counts, enabling O(1) checks for valid sub-path sums ending at the current node.

Common Pitfalls & Bugs

Failing to initialize prefix[0] = 1 causes missing valid paths that start at the root.

2

Traverse Tree with DFS While Updating and Backtracking Prefix Sums

5def dfs(node, curr_sum):
6 if not node:
7 return 0
9 curr_sum += node.val
10 count = prefix.get(curr_sum - targetSum, 0)
12 prefix[curr_sum] = prefix.get(curr_sum, 0) + 1
14 count += dfs(node.left, curr_sum)
15 count += dfs(node.right, curr_sum)
17 prefix[curr_sum] -= 1
19 return count

Objective

To recursively explore each node, update the current cumulative sum, count valid paths, and backtrack prefix sums to maintain correct state.

Key Insight

At each node, the algorithm calculates the cumulative sum and uses the prefix map to find how many prefix sums satisfy the condition for a valid path. Incrementing the prefix count before recursion and decrementing after ensures that sibling branches do not interfere with each other's prefix sums, preserving correctness.

Interview Quick-Check

Core Logic

The DFS updates the prefix map with the current sum before exploring children and decrements it after, ensuring accurate path counts without double counting.

State & Boundaries

The base case returns 0 when reaching a null node, terminating recursion.

Common Pitfalls & Bugs

Neglecting to decrement prefix counts after recursion leads to incorrect path counts due to shared state across branches.

3

Return Total Count of Valid Paths from DFS

21return dfs(root, 0)

Objective

To return the total number of valid paths found starting from the root node.

Key Insight

The initial call to dfs with the root and cumulative sum 0 triggers the entire traversal and counting process, returning the final answer.

Interview Quick-Check

Core Logic

The final return aggregates all valid paths found in the tree.

Line Analysis

This solution has 3 Critical lines interviewers watch for.

Line 10 Critical
count = prefix.get(curr_sum - targetSum, 0)

Count paths ending at current node with sum equal to targetSum.

By checking prefix sums for (curr_sum - targetSum), the algorithm identifies how many valid sub-paths end at this node, enabling O(1) counting of valid paths.

Line 17 Critical
prefix[curr_sum] -= 1

Decrement count of current cumulative sum to backtrack.

Backtracking the prefix map count after visiting children prevents sibling branches from incorrectly sharing prefix sums, preserving correctness.

Line 12 Critical
prefix[curr_sum] = prefix.get(curr_sum, 0) + 1

Increment count of current cumulative sum in prefix map.

Updating the prefix map before recursion ensures that child nodes can correctly identify valid paths that include the current node.

Line 3 Core
prefix = {0: 1}

Initialize prefix sum map with 0 sum occurring once.

This initialization accounts for paths that start at the root and sum exactly to targetSum, enabling correct counting without special cases.

Line 9 Core
curr_sum += node.val

Add current node's value to cumulative sum.

Updating the cumulative sum at each node is essential to track the sum of the path from the root to the current node.

Line 19 Core
return count

Return total count of valid paths found at this node and its subtrees.

Aggregates counts from current node and its descendants, enabling the recursive accumulation of the final answer.

Line 21 Core
return dfs(root, 0)

Start DFS traversal from root with initial sum 0 and return total count.

This call triggers the entire prefix sum DFS process, returning the total number of valid paths in the tree.

Line 6 Core
if not node:

Return 0 if the current node is null (base case).

This base case terminates recursion when leaf nodes are reached, preventing null pointer errors and signaling no paths from this branch.

Line 14 Core
count += dfs(node.left, curr_sum)

Recursively count valid paths in left subtree.

Exploring the left subtree accumulates counts of valid paths that include nodes in that branch.

Line 15 Core
count += dfs(node.right, curr_sum)

Recursively count valid paths in right subtree.

Exploring the right subtree accumulates counts of valid paths that include nodes in that branch.

Line 5 Standard
def dfs(node, curr_sum):

Define the recursive DFS helper function with current node and cumulative sum.

Encapsulating the traversal and prefix sum logic in a helper function allows clean recursion and state management.

Line 7 Standard
return 0

Return 0 immediately for null nodes.

Ensures that null nodes contribute no paths, maintaining correctness of the count aggregation.

Test Your Understanding

Why must the prefix sum count be decremented after visiting both children of a node?

Because the prefix sum map tracks cumulative sums along the current path only. Decrementing after visiting children backtracks the state to avoid counting paths from sibling branches that do not share the current node's prefix sums.

Related Problems

DFS pattern

Don't just read it. Drill it.

Reconstruct Path Sum III from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Path Sum III drill

or drill a free problem