1. 问题背景与题目解析
LeetCode 112题"路径总和"是二叉树类问题中的经典题目,也是面试中的高频考点。题目要求判断二叉树中是否存在从根节点到叶子节点的路径,使得路径上所有节点值之和等于给定的目标值。
这道题之所以重要,是因为它考察了两个核心能力:
- 对二叉树结构的理解与操作能力
- 递归思想的实际应用能力
在实际开发中,类似路径搜索的场景非常常见,比如:
- 文件系统中查找特定大小的文件
- 组织架构树中查找满足条件的汇报路径
- 游戏开发中的路径决策算法
2. 基础解法:递归DFS
2.1 递归思路解析
递归解法是最直观的解决方案,其核心思想是:
- 从根节点开始,每次递归处理当前节点
- 将目标值减去当前节点的值
- 如果当前节点是叶子节点,判断剩余值是否为0
- 如果不是叶子节点,则递归处理左右子树
def hasPathSum(root, targetSum): 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)2.2 递归实现细节
- 时间复杂度:O(n),需要访问每个节点一次
- 空间复杂度:O(h),h为树的高度,递归调用栈的深度
注意:递归解法虽然简洁,但在处理大型树时可能会遇到栈溢出问题。对于极端不平衡的树(如链表状的树),空间复杂度会退化为O(n)。
2.3 递归优化技巧
- 提前终止:当找到符合条件的路径时立即返回,不再继续搜索
- 短路求值:使用or运算符,左子树找到结果就不搜索右子树
- 尾递归优化:某些语言支持尾递归优化,可以避免栈溢出
3. 进阶解法:迭代DFS
3.1 迭代思路解析
迭代解法使用显式的栈来模拟递归过程:
- 使用栈存储节点和对应的剩余目标值
- 每次从栈中弹出一个节点和剩余值
- 如果是叶子节点且剩余值等于节点值,返回True
- 否则将左右子节点和新的剩余值压入栈中
def hasPathSum(root, targetSum): if not root: return False stack = [(root, targetSum)] while stack: node, curr_sum = stack.pop() if not node.left and not node.right and curr_sum == node.val: return True if node.right: stack.append((node.right, curr_sum - node.val)) if node.left: stack.append((node.left, curr_sum - node.val)) return False3.2 迭代实现细节
- 时间复杂度:O(n),同样需要访问每个节点
- 空间复杂度:O(h),栈的最大深度为树的高度
提示:迭代解法中节点的处理顺序会影响搜索顺序。上述代码使用后进先出的栈,实现的是深度优先搜索。如果使用队列(先进先出),则变为广度优先搜索。
3.3 迭代与递归的选择
选择依据:
- 树的高度较大时,优先选择迭代解法避免栈溢出
- 代码简洁性要求高时,选择递归解法
- 需要控制搜索顺序时,迭代解法更灵活
4. 边界条件与异常处理
4.1 常见边界情况
- 空树:直接返回False
- 单节点树:判断节点值是否等于目标值
- 负数值节点:不影响算法正确性
- 目标值为0:只有当树为空时才返回True
4.2 测试用例设计
完整的测试应包含:
# 空树 assert hasPathSum(None, 0) == False # 单节点树 root = TreeNode(5) assert hasPathSum(root, 5) == True assert hasPathSum(root, 6) == False # 多节点树 root = TreeNode(5) root.left = TreeNode(4) root.right = TreeNode(8) root.left.left = TreeNode(11) root.left.left.left = TreeNode(7) root.left.left.right = TreeNode(2) root.right.left = TreeNode(13) root.right.right = TreeNode(4) root.right.right.right = TreeNode(1) assert hasPathSum(root, 22) == True assert hasPathSum(root, 26) == True assert hasPathSum(root, 18) == False5. 算法扩展与变种
5.1 返回所有满足条件的路径
LeetCode 113题是本题的扩展,要求返回所有满足条件的路径。解法思路类似,但需要维护当前路径:
def pathSum(root, targetSum): def dfs(node, curr_sum, path, res): if not node: return if not node.left and not node.right and curr_sum == node.val: res.append(path + [node.val]) return dfs(node.left, curr_sum - node.val, path + [node.val], res) dfs(node.right, curr_sum - node.val, path + [node.val], res) res = [] dfs(root, targetSum, [], res) return res5.2 任意节点开始的路径
LeetCode 437题进一步扩展,路径不需要从根节点开始。这需要双重递归:
def pathSum(root, targetSum): if not root: return 0 return (dfs(root, targetSum) + pathSum(root.left, targetSum) + pathSum(root.right, targetSum)) def dfs(node, curr_sum): if not node: return 0 return (int(curr_sum == node.val) + dfs(node.left, curr_sum - node.val) + dfs(node.right, curr_sum - node.val))5.3 实际应用场景
- 文件系统搜索:查找特定大小的文件路径
- 组织架构分析:查找满足条件的汇报路径
- 游戏AI决策:评估不同路径的得分
6. 性能优化与进阶思考
6.1 记忆化优化
对于重复计算的子树,可以使用记忆化技术存储中间结果:
from functools import lru_cache @lru_cache(maxsize=None) def hasPathSum(root, targetSum): # 实现略6.2 并行计算
对于大型树,可以考虑并行处理左右子树:
from concurrent.futures import ThreadPoolExecutor def hasPathSum(root, targetSum): if not root: return False if not root.left and not root.right: return targetSum == root.val with ThreadPoolExecutor() as executor: left = executor.submit(hasPathSum, root.left, targetSum - root.val) right = executor.submit(hasPathSum, root.right, targetSum - root.val) return left.result() or right.result()6.3 算法选择建议
- 小型树:优先选择递归解法,代码简洁
- 大型树:选择迭代解法,避免栈溢出
- 需要所有路径:使用回溯法
- 并行环境:考虑并行计算优化
7. 常见错误与调试技巧
7.1 典型错误
- 忘记处理空树情况
- 错误判断叶子节点(使用or而不是and)
- 递归终止条件错误
- 迭代解法中节点处理顺序错误
7.2 调试方法
- 打印递归调用树
def hasPathSum(root, targetSum, depth=0): print(" "*depth + f"Node: {root.val if root else None}, Target: {targetSum}") # 其余代码不变- 可视化二叉树结构
def printTree(root, level=0): if root: printTree(root.right, level + 1) print(" "*level + str(root.val)) printTree(root.left, level + 1)- 使用小规模测试用例逐步验证
8. 相关题目推荐
简单难度:
- 二叉树的最大深度
- 平衡二叉树
中等难度:
- 路径总和 II
- 求根到叶子节点数字之和
困难难度:
- 二叉树中的最大路径和
- 路径总和 III
9. 学习资源与进阶路径
推荐书籍:
- 《算法导论》树结构章节
- 《编程珠玑》算法优化部分
在线课程:
- LeetCode二叉树专题
- Coursera算法专项课程
练习建议:
- 先掌握基础遍历方法
- 再学习递归思想
- 最后研究优化技巧
10. 面试技巧与实战建议
面试常见问题:
- 解释算法时间/空间复杂度
- 如何处理边界条件
- 递归和迭代的区别
回答策略:
- 先说明解题思路
- 再写代码
- 最后分析复杂度
实战建议:
- 先写测试用例
- 考虑异常情况
- 讨论优化空间