GitHub_Trending/le/LeetCode-Book:二叉树中和为某一值的路径
问题定义与核心挑战
二叉树路径求和问题(Path Sum)是算法面试中的高频考点,其核心要求是找到从根节点到叶节点的所有路径,使得路径上各节点值的总和等于目标值。该问题同时考察了二叉树的遍历技巧与路径记录能力,常见于《剑指 Offer》第34题与LeetBook《图解算法数据结构》LCR 153题。
问题特征分析
- 结构约束:必须是从根节点到叶节点的完整路径(叶节点指左右子树均为空的节点)
- 多解可能性:可能存在多条满足条件的路径,需返回所有有效路径集合
- 边界条件:空树、单节点树、路径和为负数等特殊场景需特殊处理
算法设计:回溯法求解框架
核心思路
采用先序遍历+路径回溯的经典组合策略:
- 先序遍历:按"根→左→右"顺序遍历树结构,确保每条路径从根节点开始
- 路径记录:使用栈/列表实时维护当前路径,满足条件时保存路径副本
- 回溯机制:从子节点返回父节点前,移除当前节点值以恢复路径状态
算法流程图
关键步骤解析
-
路径状态管理
- 使用动态数组
path存储当前路径 - 节点访问时入栈(
path.append),回溯时出栈(path.pop) - 满足条件时需创建路径副本(
list(path))避免引用传递问题
- 使用动态数组
-
目标值递减策略
- 每次访问节点时从目标值中减去节点值
- 叶节点处判断剩余目标值是否为0,避免重复求和计算
多语言实现代码对比
Python实现
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: TreeNode, targetSum: int) -> list[list[int]]:
result, path = [], []
def backtrack(node: TreeNode, remaining: int) -> None:
if not node:
return
# 加入当前节点到路径
path.append(node.val)
remaining -= node.val
# 叶节点且路径和达标
if not node.left and not node.right and remaining == 0:
result.append(list(path)) # 关键: 创建副本而非引用
# 递归遍历子树
backtrack(node.left, remaining)
backtrack(node.right, remaining)
# 回溯: 移除当前节点
path.pop()
backtrack(root, targetSum)
return result
Java实现
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
backtrack(root, targetSum);
return result;
}
private void backtrack(TreeNode node, int remaining) {
if (node == null) return;
path.add(node.val);
remaining -= node.val;
// 叶节点判断需同时检查左右子树是否为空
if (node.left == null && node.right == null && remaining == 0) {
result.add(new LinkedList<>(path)); // 创建新列表避免引用问题
}
backtrack(node.left, remaining);
backtrack(node.right, remaining);
path.removeLast(); // 回溯操作
}
}
C++实现
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtrack(TreeNode* node, int remaining) {
if (!node) return;
path.push_back(node->val);
remaining -= node->val;
// 叶节点条件判断
if (!node->left && !node->right && remaining == 0) {
result.push_back(path); // vector自动拷贝
}
backtrack(node->left, remaining);
backtrack(node->right, remaining);
path.pop_back(); // 回溯
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
backtrack(root, targetSum);
return result;
}
};
复杂度分析与优化
基础复杂度
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(N) | 需遍历所有N个节点 |
| 空间复杂度 | O(N) | 最差情况(链表)路径存储N个节点 |
优化方向
- 剪枝策略:当路径和超过目标值时提前终止(仅适用于非负权值树)
# 剪枝优化版本
def backtrack(node: TreeNode, remaining: int) -> None:
if not node:
return
# 提前剪枝: 若当前值已超过剩余目标且节点值为正
if node.val > remaining and node.val > 0:
return
# 后续逻辑同上...
- 迭代实现:使用栈模拟递归过程,避免递归栈溢出
# 迭代版实现
def pathSum(root: TreeNode, targetSum: int) -> list[list[int]]:
if not root:
return []
result = []
stack = [(root, targetSum - root.val, [root.val])]
while stack:
node, remaining, path = stack.pop()
# 叶节点判断
if not node.left and not node.right and remaining == 0:
result.append(path)
# 右子树先入栈(保证左子树先处理)
if node.right:
stack.append((node.right, remaining - node.right.val, path + [node.right.val]))
if node.left:
stack.append((node.left, remaining - node.left.val, path + [node.left.val]))
return result
典型测试案例与易错点
测试用例设计
| 测试场景 | 输入特征 | 预期输出 |
|---|---|---|
| 标准二叉树 | 根节点5,目标和22 | [[5,4,11,2],[5,8,4,5]] |
| 单节点树 | 节点值1,目标和1 | [[1]] |
| 无有效路径 | 根节点1,目标和2 | [] |
| 包含负数节点 | 节点值[-2,null,-3],目标和-5 | [[-2,-3]] |
| 空树 | null,任意目标值 | [] |
常见错误分析
-
路径拷贝问题
- 错误:
result.append(path)直接添加引用 - 正确:
result.append(list(path))创建副本
- 错误:
-
叶节点判断错误
- 错误:仅判断
node.left is None或node.right is None - 正确:需同时判断左右子树均为空
not node.left and not node.right
- 错误:仅判断
-
回溯逻辑缺失
- 错误:忘记在递归返回后执行
path.pop() - 后果:路径包含已回溯节点,导致结果错误
- 错误:忘记在递归返回后执行
实际应用与扩展问题
算法应用场景
- 路径规划系统:寻找满足资源约束的路径
- XML/JSON文档解析:提取满足特定条件的节点路径
- 版本控制系统:查找提交历史中满足变更量要求的提交链
扩展问题变种
- 路径总和III:允许路径从任意节点开始,任意节点结束
- 二叉树最大路径和:寻找非空路径(不一定经过根节点)的最大和
- 路径求和IV:给定节点编号序列,计算路径和
学习资源与进阶建议
推荐练习题目
- 基础:LeetCode 112.路径总和(判断是否存在有效路径)
- 进阶:LeetCode 437.路径总和III(任意节点起始)
- 挑战:LeetCode 124.二叉树中的最大路径和
参考资料
- 《剑指Offer》第34题解析
- LeetBook《图解算法数据结构》LCR 153题
- 官方代码仓库:https://gitcode.com/GitHub_Trending/le/LeetCode-Book
总结与思考
二叉树路径求和问题展示了回溯算法在树形结构中的经典应用,核心在于路径状态的动态管理与递归过程中的状态恢复。通过该问题的学习,可以深入理解:
- 先序遍历在路径探索中的引导作用
- 回溯法的"选择-探索-撤销"三步框架
- 复杂数据结构操作中的深拷贝与浅拷贝问题
建议通过不同解法的对比实现(递归vs迭代)、不同语言的特性差异(如Python列表引用与C++向量拷贝),加深对算法本质的理解,为解决更复杂的树路径问题奠定基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



