1. 题目解析与核心思路
LeetCode 112题"路径总和"是二叉树类问题中的经典题目,考察递归和深度优先搜索(DFS)的应用能力。题目要求判断二叉树中是否存在从根节点到叶子节点的路径,使得路径上所有节点值之和等于给定的目标和。
1.1 题目理解要点
题目给出的二叉树结构定义如下:
python复制class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
关键条件需要特别注意:
- 路径必须从根节点开始,到叶子节点结束
- 叶子节点是指没有子节点的节点
- 路径总和是节点值的累加和
- 题目只需要判断是否存在这样的路径,不需要找出所有路径
1.2 基础解法思路
最直观的解法是使用递归深度优先搜索:
- 从根节点开始,递归检查左右子树
- 每次递归时将目标和减去当前节点值
- 当到达叶子节点时,检查剩余目标和是否为0
- 只要左子树或右子树任一满足条件即可返回True
这种解法的时间复杂度是O(n),其中n是树中节点数量,因为每个节点最多被访问一次。空间复杂度在最坏情况下(树退化为链表)是O(n),平均情况下是O(logn),取决于树的深度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 递归解法详解与优化
2.1 标准递归实现
以下是Python的标准递归实现:
python复制def hasPathSum(root: TreeNode, targetSum: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return targetSum == root.val
return hasPathSum(root.left, targetSum - root.val) or \
hasPathSum(root.right, targetSum - root.val)
这个实现有几个关键点:
- 空树直接返回False
- 遇到叶子节点时比较剩余目标和与节点值
- 非叶子节点则递归检查左右子树
2.2 递归优化技巧
在实际编码中可以做一些优化:
- 提前终止:当发现某条路径满足条件时,可以立即返回,不需要继续检查其他路径
- 短路求值:使用or运算符的短路特性,左子树满足时不会计算右子树
- 边界处理:对于空树的处理要放在最前面,避免后续操作出现空指针异常
注意:递归解法虽然简洁,但在树很深时可能导致栈溢出。对于极端情况的大树,需要考虑使用迭代解法。
3. 迭代解法与性能对比
3.1 基于DFS的迭代实现
使用栈来实现深度优先搜索的迭代版本:
python复制def hasPathSum(root: TreeNode, targetSum: int) -> bool:
if not root:
return False
stack = [(root, targetSum - root.val)]
while stack:
node, curr_sum = stack.pop()
if not node.left and not node.right and curr_sum == 0:
return True
if node.right:
stack.append((node.right, curr_sum - node.right.val))
if node.left:
stack.append((node.left, curr_sum - node.left.val))
return False
这个实现的特点:
- 使用栈模拟递归调用
- 存储节点和当前剩余和两个信息
- 右子节点先入栈,保证左子节点先处理(DFS顺序)
- 遇到叶子节点且剩余和为0时立即返回
3.2 递归与迭代的性能对比
| 特性 | 递归解法 | 迭代解法 |
|---|---|---|
| 代码简洁性 | 高 | 中 |
| 空间复杂度 | O(n)最坏情况 | O(n)最坏情况 |
| 栈溢出风险 | 有 | 无 |
| 执行速度 | 稍慢(函数调用开销) | 稍快 |
| 调试难度 | 较难 | 较易 |
对于面试场景,通常推荐先给出递归解法,然后讨论是否可以优化为迭代解法,展示全面的思考过程。
4. 常见错误与边界情况
4.1 新手常见错误
- 忽略空树情况:直接访问root.val导致空指针异常
- 错误判断叶子节点:只检查一个子节点是否为空
- 路径定义错误:认为任意节点间的路径都符合要求
- 和的计算错误:忘记在递归时更新剩余和
- 过早返回:在未检查所有可能路径时就返回结果
4.2 必须考虑的边界情况
- 空树:应该返回False
- 单节点树:直接比较节点值与目标和
- 负数值节点:路径和可能先增后减
- 大数节点:注意数值溢出问题(但在Python中不常见)
- 不平衡树:可能导致递归深度过大
4.3 测试用例设计建议
完整的测试应包含以下情况:
python复制# 空树
assert hasPathSum(None, 0) == False
# 单节点且满足
assert hasPathSum(TreeNode(5), 5) == True
# 单节点不满足
assert hasPathSum(TreeNode(5), 6) == False
# 多节点存在路径
tree = TreeNode(5, TreeNode(4, TreeNode(11, TreeNode(7), TreeNode(2))), TreeNode(8, TreeNode(13), TreeNode(4, None, TreeNode(1))))
assert hasPathSum(tree, 22) == True
# 多节点不存在路径
assert hasPathSum(tree, 100) == False
5. 算法扩展与变种问题
5.1 返回所有满足条件的路径
LeetCode 113题是本题的扩展,要求返回所有满足条件的路径。解法需要维护当前路径:
python复制def pathSum(root: TreeNode, targetSum: int) -> List[List[int]]:
def dfs(node, curr_sum, path, result):
if not node:
return
path.append(node.val)
if not node.left and not node.right and curr_sum == node.val:
result.append(list(path))
dfs(node.left, curr_sum - node.val, path, result)
dfs(node.right, curr_sum - node.val, path, result)
path.pop()
result = []
dfs(root, targetSum, [], result)
return result
5.2 任意节点开始的路径和
另一个变种是路径不需要从根节点开始,也不需要在叶子节点结束。这类问题通常需要双重递归:
- 外层递归遍历每个节点
- 内层递归计算以当前节点为起点的所有路径和
5.3 路径和的三层抽象理解
- 第一层:根到叶子的路径和(本题)
- 第二层:任意节点到任意节点的路径和
- 第三层:路径定义为向上或向下的任意方向
理解这三层抽象有助于解决更复杂的路径和问题。
6. 实际应用与工程实践
6.1 文件系统路径匹配
这个问题可以类比文件系统中查找特定大小的文件:
- 目录结构就是一棵树
- 文件大小就是节点值
- 查找从根目录到某个文件的路径大小总和
6.2 决策树应用
在机器学习决策树中,可能需要判断是否存在某条决策路径满足特定条件总和,算法思路与此题类似。
6.3 工程实现的注意事项
- 对于大型树结构,迭代解法更安全
- 在实际工程中,可能需要在树节点中添加父指针方便回溯
- 可以缓存部分计算结果优化性能
- 多线程环境下需要注意数据同步问题
7. 刷题策略与学习建议
7.1 二叉树问题解题框架
- 确定遍历顺序(前序、中序、后序)
- 确定递归终止条件
- 处理当前节点
- 递归处理子节点
- 考虑是否需要回溯
7.2 本题的思维训练价值
- 递归思维的培养
- 树结构的理解
- 边界条件处理能力
- 递归转迭代的能力
- 算法优化意识
7.3 相关题目推荐
- LeetCode 113 - 路径总和 II
- LeetCode 437 - 路径总和 III
- LeetCode 124 - 二叉树中的最大路径和
- LeetCode 129 - 求根到叶子节点数字之和
- LeetCode 543 - 二叉树的直径
对于二叉树类问题,建议先掌握前中后序遍历的递归和迭代写法,再学习层次遍历,最后攻克路径类问题。路径总和问题是很好的递归算法入门题,理解后可以尝试解决更复杂的变种问题。
