LC113 · 路径总和 II · 工程执行实验室
路径收集台 · DFS + 回溯
LC112 问有没有一条路;LC113 问把所有正确路线都交出来。核心是一路维护 path,到叶子判断,命中就复制一份,再回溯撤销。
LC112 vs LC113
判断是否存在一条 root-to-leaf 路径和为 target,找到即可返回 true。
返回所有满足条件的 root-to-leaf 路径,必须遍历完整棵树,不能提前结束。
LC112 问有没有一条路;LC113 问把所有正确路线都交出来。所以这题的核心不是返回 true,而是一路维护 path,到叶子判断,命中就复制一份,再回溯撤销。
- 1. 进入:path.push(val),remain -= val
- 2. 叶判断:是叶子且 remain === 0 → ans.push([...path])
- 3. 递归左右子树(不提前 return)
- 4. 退出:path.pop() 恢复现场
- LC112路径总和 I
判断是否存在,可提前返回 true。
- LC437路径总和 III
任意向下路径,不要求到叶子。
- LC257二叉树的所有路径
同样 DFS + 回溯收集所有 root-to-leaf 路径。
- · LC112:存在一条 root-to-leaf 路径和为 target 即可返回 true,找到可提前结束。
- · LC113:返回所有满足条件的 root-to-leaf 路径,必须遍历完整棵树,不能提前 return。
LC112 vs LC113:有没有 vs 全部交出来
LC112 判断是否存在一条 root-to-leaf 路径和为 target,找到即可返回 true。LC113 要返回所有满足 targetSum 的 root-to-leaf 路径列表——不能提前结束,必须遍历完整棵树。
LC112 问有没有一条路;LC113 问把所有正确路线都交出来。所以这题的核心不是返回 true,而是一路维护 path,到叶子判断,命中就复制一份,再回溯撤销。
- LC112:存在一条 root-to-leaf 路径和为 target 即可返回 true,找到可提前结束。
- LC113:返回所有满足条件的 root-to-leaf 路径,必须遍历完整棵树,不能提前 return。
- 进入节点:path.push(val),remain -= val。
- 叶子且 remain === 0:ans.push([...path]),必须复制 path。
- 退出节点:path.pop(),恢复现场,兄弟分支才能从正确前缀出发。
LC112 问有没有一条路;LC113 问把所有正确路线都交出来。所以这题的核心不是返回 true,而是一路维护 path,到叶子判断,命中就复制一份,再回溯撤销。
主状态 · node / remain / path / ans
递归栈 · dfs(node, remain)
(空栈)
1function pathSum(root, targetSum) {2 const ans = []3 const path = []5 function dfs(node, remain) {6 if (!node) return8 path.push(node.val)9 remain -= node.val11 if (!node.left && !node.right && remain === 0) {12 ans.push([...path])13 }15 dfs(node.left, remain)16 dfs(node.right, remain)18 path.pop()19 }21 dfs(root, targetSum)22 return ans23}
为什么要复制 path?
ans.push(path)ans.push(path) — 保存的是同一个数组引用。后续回溯 path.pop() 时,ans 里的内容也会被改掉,最后可能变成空数组或错误路径。
ans.push([...path])ans.push([...path]) — 给当前路径拍一张快照,后续 path 怎么变化,答案里的路径都不会变。
- 时间
- O(N + K·H),最坏 O(N²)
- 辅助空间
- O(H) 辅助空间
- 含输出
- 输出计入时:O(K·H),最坏 O(N²)
N 为节点数:DFS 每个节点访问一次。K 为满足条件的路径条数,每收集一条需复制当前 path,成本 O(H),H 为树高。
递归栈深度与当前 path 长度均为 O(H)。若把输出结果计入空间,则为 O(K·H),最坏 O(N²)。
这题用 DFS + 回溯。递归过程中维护一条从根到当前节点的 path,同时维护剩余目标 remain。进入节点时把值加入 path,并扣减 remain;如果当前节点是叶子且 remain 为 0,就把 path 的副本加入答案。递归左右子树后,需要 pop 当前节点,恢复现场,保证兄弟分支不会受到影响。时间复杂度是 O(N + K·H),最坏 O(N²),辅助空间 O(H)。
内部节点 remain 也可能为 0,但路径必须到叶子才算。必须同时检查 !left && !right。
LC112 可以短路;LC113 要收集全部路径,必须遍历完整棵树。
ans.push(path) — 保存的是同一个数组引用。后续回溯 path.pop() 时,ans 里的内容也会被改掉,最后可能变成空数组或错误路径。
回溯不 pop,path 会带着上一分支的节点进入兄弟子树,答案全错。
任意向下路径是 LC437 的范围;本题必须走到叶子。