LeetCode 437: Path Sum III (Prefix Sum + DFS Backtracking)
LeetCode 437Binary TreePrefix SumToday we solve LeetCode 437 - Path Sum III.
Source: https://leetcode.com/problems/path-sum-iii/
English
Problem Summary
Given a binary tree and an integer targetSum, count how many downward paths (from parent to child) have sum exactly equal to targetSum. A path can start and end at any nodes, as long as it goes downward.
Key Insight
If current root-to-node prefix sum is curr, then any previous prefix sum equal to curr - targetSum forms a valid path ending at current node. So during DFS we keep a hash map: prefixCount[prefixSum].
Brute Force and Limitations
Brute force starts DFS from every node and explores all downward sums. That is O(n^2) in skewed trees.
Optimal Algorithm Steps
1) Initialize prefixCount[0] = 1 (empty prefix before root).
2) DFS with running sum curr.
3) Add prefixCount[curr - targetSum] to answer.
4) Increment prefixCount[curr], recurse left/right.
5) Decrement prefixCount[curr] when backtracking.
Complexity Analysis
Time: O(n).
Space: O(n) for recursion stack + prefix map in worst case.
Common Pitfalls
- Forgetting backtracking decrement, causing sibling contamination.
- Missing initialization prefixCount[0] = 1.
- Using int for prefix sums when values can overflow; use wider type when needed.
Reference Implementations (Java / Go / C++ / Python / JavaScript)
class Solution {
private int ans = 0;
private int target;
private Map<Long, Integer> cnt = new HashMap<>();
public int pathSum(TreeNode root, int targetSum) {
target = targetSum;
cnt.put(0L, 1);
dfs(root, 0L);
return ans;
}
private void dfs(TreeNode node, long curr) {
if (node == null) return;
curr += node.val;
ans += cnt.getOrDefault(curr - target, 0);
cnt.put(curr, cnt.getOrDefault(curr, 0) + 1);
dfs(node.left, curr);
dfs(node.right, curr);
cnt.put(curr, cnt.get(curr) - 1);
}
}func pathSum(root *TreeNode, targetSum int) int {
prefix := map[int64]int{0: 1}
ans := 0
var dfs func(*TreeNode, int64)
dfs = func(node *TreeNode, curr int64) {
if node == nil {
return
}
curr += int64(node.Val)
ans += prefix[curr-int64(targetSum)]
prefix[curr]++
dfs(node.Left, curr)
dfs(node.Right, curr)
prefix[curr]--
}
dfs(root, 0)
return ans
}class Solution {
public:
int ans = 0;
unordered_map<long long, int> cnt;
int pathSum(TreeNode* root, int targetSum) {
cnt[0] = 1;
dfs(root, 0LL, targetSum);
return ans;
}
void dfs(TreeNode* node, long long curr, int target) {
if (!node) return;
curr += node->val;
if (cnt.count(curr - target)) ans += cnt[curr - target];
cnt[curr]++;
dfs(node->left, curr, target);
dfs(node->right, curr, target);
cnt[curr]--;
}
};class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
prefix = {0: 1}
ans = 0
def dfs(node: Optional[TreeNode], curr: int) -> None:
nonlocal ans
if not node:
return
curr += node.val
ans += prefix.get(curr - targetSum, 0)
prefix[curr] = prefix.get(curr, 0) + 1
dfs(node.left, curr)
dfs(node.right, curr)
prefix[curr] -= 1
dfs(root, 0)
return ansvar pathSum = function(root, targetSum) {
const prefix = new Map();
prefix.set(0, 1);
let ans = 0;
const dfs = (node, curr) => {
if (!node) return;
curr += node.val;
ans += (prefix.get(curr - targetSum) || 0);
prefix.set(curr, (prefix.get(curr) || 0) + 1);
dfs(node.left, curr);
dfs(node.right, curr);
prefix.set(curr, prefix.get(curr) - 1);
};
dfs(root, 0);
return ans;
};中文
题目概述
给定一棵二叉树和整数 targetSum,统计所有“向下路径”(父到子)中,路径和等于 targetSum 的数量。路径可以从任意节点开始,到任意后代结束。
核心思路
在 DFS 过程中维护当前从根到节点的前缀和 curr。若之前出现过前缀和 curr - targetSum,就说明存在若干条以当前节点结尾的合法路径。用哈希表记录每个前缀和出现次数即可。
暴力解法与不足
暴力法对每个节点都重新做一次向下搜索,最坏会退化到 O(n^2)。
最优算法步骤
1)初始化 prefixCount[0] = 1(表示根之前的空前缀)。
2)DFS 到当前节点后更新 curr += node.val。
3)答案累加 prefixCount[curr - targetSum]。
4)把当前前缀和计数加一后继续递归左右子树。
5)回溯时把当前前缀和计数减一,避免影响兄弟分支。
复杂度分析
时间复杂度:O(n)。
空间复杂度:O(n)(哈希表与递归栈最坏情况)。
常见陷阱
- 忘记在回溯时减去当前前缀和计数,导致跨分支串扰。
- 忘记初始化 prefixCount[0] = 1。
- 前缀和可能较大,建议用长整型避免溢出。
多语言参考实现(Java / Go / C++ / Python / JavaScript)
class Solution {
private int ans = 0;
private int target;
private Map<Long, Integer> cnt = new HashMap<>();
public int pathSum(TreeNode root, int targetSum) {
target = targetSum;
cnt.put(0L, 1);
dfs(root, 0L);
return ans;
}
private void dfs(TreeNode node, long curr) {
if (node == null) return;
curr += node.val;
ans += cnt.getOrDefault(curr - target, 0);
cnt.put(curr, cnt.getOrDefault(curr, 0) + 1);
dfs(node.left, curr);
dfs(node.right, curr);
cnt.put(curr, cnt.get(curr) - 1);
}
}func pathSum(root *TreeNode, targetSum int) int {
prefix := map[int64]int{0: 1}
ans := 0
var dfs func(*TreeNode, int64)
dfs = func(node *TreeNode, curr int64) {
if node == nil {
return
}
curr += int64(node.Val)
ans += prefix[curr-int64(targetSum)]
prefix[curr]++
dfs(node.Left, curr)
dfs(node.Right, curr)
prefix[curr]--
}
dfs(root, 0)
return ans
}class Solution {
public:
int ans = 0;
unordered_map<long long, int> cnt;
int pathSum(TreeNode* root, int targetSum) {
cnt[0] = 1;
dfs(root, 0LL, targetSum);
return ans;
}
void dfs(TreeNode* node, long long curr, int target) {
if (!node) return;
curr += node->val;
if (cnt.count(curr - target)) ans += cnt[curr - target];
cnt[curr]++;
dfs(node->left, curr, target);
dfs(node->right, curr, target);
cnt[curr]--;
}
};class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
prefix = {0: 1}
ans = 0
def dfs(node: Optional[TreeNode], curr: int) -> None:
nonlocal ans
if not node:
return
curr += node.val
ans += prefix.get(curr - targetSum, 0)
prefix[curr] = prefix.get(curr, 0) + 1
dfs(node.left, curr)
dfs(node.right, curr)
prefix[curr] -= 1
dfs(root, 0)
return ansvar pathSum = function(root, targetSum) {
const prefix = new Map();
prefix.set(0, 1);
let ans = 0;
const dfs = (node, curr) => {
if (!node) return;
curr += node.val;
ans += (prefix.get(curr - targetSum) || 0);
prefix.set(curr, (prefix.get(curr) || 0) + 1);
dfs(node.left, curr);
dfs(node.right, curr);
prefix.set(curr, prefix.get(curr) - 1);
};
dfs(root, 0);
return ans;
};
Comments