TL;DR
Prefix-sum hash map carried down a DFS β O(n) time, O(n) space.
Approach 1 β Brute force: try every starting node
Every downward path starts somewhere. So: for each node in the tree, run a
second DFS downward from it, extending the running sum and counting every time
it hits targetSum. (Donβt stop at the first hit β negatives mean the sum can
return to the target further down.)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
def count_from(node: Optional[TreeNode], remaining: int) -> int:
if not node:
return 0
hits = 1 if node.val == remaining else 0
rest = remaining - node.val
return (hits
+ count_from(node.left, rest)
+ count_from(node.right, rest))
if not root:
return 0
return (count_from(root, targetSum)
+ self.pathSum(root.left, targetSum)
+ self.pathSum(root.right, targetSum))
Complexity: O(nΒ²) time in the worst case (each of n starting nodes scans its
whole subtree β think of a skewed chain), O(h) space. With n β€ 1000 it
passes, but it recomputes the same running sums over and over: the path
5 β 2 β 1 is re-summed once for each of its three possible starts.
Approach 2 β Prefix sums on the root path (the classic)
The insight: this is the tree version of count subarrays summing to k.
Let prefix(v) be the sum of values from the root down to node v. A
downward path from aβs childβ¦ down to b sums to targetSum exactly when
prefix(b) - prefix(a) = targetSum, i.e. when some ancestor prefix equals
prefix(b) - targetSum. So DFS down the tree maintaining a hash map
count[s] = how many nodes on the current root-to-here path have prefix sum
s. At each node, count answers βhow many valid paths end here?β in O(1).
The subtlety unique to trees: when the DFS leaves a node, its prefix must be
removed from the map, or a prefix from the left subtree would be visible
while exploring the right subtree β and left-branch prefixes are not
ancestors of right-branch nodes.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
from collections import defaultdict
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
count: DefaultDict[int, int] = defaultdict(int)
count[0] = 1 # the empty prefix: paths starting at the root
def dfs(node: Optional[TreeNode], running: int) -> int:
if not node:
return 0
running += node.val
paths_ending_here = count[running - targetSum]
count[running] += 1
total = (paths_ending_here
+ dfs(node.left, running)
+ dfs(node.right, running))
count[running] -= 1 # backtrack: leave no trace for siblings
return total
return dfs(root, 0)
Walkthrough on root = [1,-1,null,1], targetSum = 0 (the left-leaning
chain 1 β β1 β 1). Map starts {0: 1}.
- Node
1: running = 1. Need 1 - 0 = 1 β count is 0 hits. Map {0:1, 1:1}.
- Node
-1: running = 0. Need 0 β map has 1 β one path found
(1 β -1). Map {0:2, 1:1}.
- Node
1: running = 1. Need 1 β map has 1 β one path found
(-1 β 1 β the ancestor prefix 1 after the root). Map {0:2, 1:2}.
- Unwind, decrementing each entry. Total = 2. Matches the expected output.
Complexity: O(n) time β one visit per node with O(1) map work; O(n) space for
the map plus O(h) recursion stack (the map holds at most h+1 distinct live
counts, but up to n entries transiently on different paths).
This is the classical prefix sum + hash map technique (the array version
is LeetCode 560, Subarray Sum Equals K), grafted onto a DFS with backtracking.
Common pitfalls
- Forgetting
count[0] = 1 β paths that start at the root itself are counted
by the empty prefix.
- Not decrementing on the way back up: prefixes from one subtree then
contaminate the sibling subtree, overcounting.
- Early-returning when the running sum hits the target β with negative values
a longer path below can hit it again.
- Sums up to 1000 nodes Γ 10^9 magnitude overflow 32-bit integers β a real
bug in C++/Java; Pythonβs arbitrary-precision ints hide it, so mention it
in an interview anyway.
Pattern takeaway
A root-to-node path is a prefix, so array prefix-sum tricks transfer to
trees. Whenever a tree problem asks about contiguous downward segments
(count paths with sum k, longest path with property P), carry the running
prefix down a DFS and a hash map of ancestor prefixes β and remember the one
tree-specific move: undo the map entry when backtracking.