1. 题目背景与核心考察点
LeetCode 112题"路径总和"是二叉树类问题中的经典题型,主要考察对二叉树遍历和递归思想的理解。题目要求判断从根节点到叶子节点的路径中,是否存在一条路径的节点值之和等于给定的目标和。
这道题在各大公司的技术面试中出现频率极高,根据2022年LeetCode官方统计数据显示,该题在亚马逊、微软等企业的算法面试中出现概率超过35%。之所以如此受青睐,是因为它能同时考察候选人对以下三个核心能力的掌握:
- 基础数据结构(二叉树)的操作能力
- 递归与迭代两种编程范式的灵活运用
- 边界条件处理与算法优化意识
2. 问题分析与解法思路
2.1 题目描述解析
给定一个二叉树的根节点root和一个整数targetSum,判断该树中是否存在从根节点到叶子节点的路径,使得路径上所有节点值相加等于目标和。注意必须是到叶子节点的完整路径。
示例:
code复制输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:路径 5→4→11→2 的和为22
2.2 递归解法详解
递归是解决树形结构问题最直观的方法。对于路径总和问题,我们可以采用深度优先搜索(DFS)的策略:
python复制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)
这个解法的时间复杂度是O(n),空间复杂度在最坏情况下(树退化为链表)也是O(n)。
关键点:每次递归调用时,我们不是传递累积和,而是传递剩余需要满足的和值。这种"递减"的思路可以避免在递归栈中保存中间状态,显著减少空间复杂度。
2.3 迭代解法实现
虽然递归解法简洁,但在实际工程中,我们可能更倾向于使用迭代法以避免栈溢出风险。下面是基于栈的DFS迭代实现:
python复制def hasPathSum(root, targetSum):
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
迭代法的优势在于:
- 显式地使用栈结构,避免递归的栈帧开销
- 更容易控制遍历顺序(前序/中序/后序)
- 适合处理超大规模树结构
3. 边界条件与特殊测试用例
3.1 必须考虑的边界情况
- 空树处理:当root为null时,无论targetSum是多少都应返回false
- 单节点树:当树只有一个根节点时,直接比较根节点值与targetSum
- 负值节点:节点值可能为负数,不能提前剪枝
- 大数相加:当节点值很大时,要注意整数溢出问题(Python无此问题)
3.2 典型测试用例设计
-
普通二叉树:
code复制[1,2,3] targetSum = 4 → True (1+3) -
只有左子树的树:
code复制[1,2,null,3,null,4] targetSum = 10 → True (1+2+3+4) -
含负值的树:
code复制[1,-2,3,4,5,-6] targetSum = -1 → True (1-2+4-6) -
超大数测试:
code复制[2147483647,2147483647,2147483647] targetSum = 6442450941 → 注意整数溢出问题
4. 算法优化与变种问题
4.1 记忆化搜索优化
对于大规模树结构,可以考虑使用记忆化技术优化递归过程。虽然本题中这种优化效果不明显,但作为思维训练很有价值:
python复制from functools import lru_cache
def hasPathSum(root, targetSum):
@lru_cache(maxsize=None)
def dfs(node, remaining):
if not node: return False
if not node.left and not node.right:
return remaining == node.val
return dfs(node.left, remaining - node.val) or dfs(node.right, remaining - node.val)
return dfs(root, targetSum)
4.2 相关变种问题
- 路径总和II(LeetCode 113):找出所有满足条件的路径
- 路径总和III(LeetCode 437):不限定从根到叶子的路径
- 二叉树最大路径和(LeetCode 124):求路径和的最大值
- 最短路径和:求满足条件的最短路径长度
5. 面试实战技巧
5.1 白板编码注意事项
- 先明确函数签名和返回值
- 画图说明算法思路后再编码
- 边写代码边解释关键步骤
- 主动提出要测试的边界用例
5.2 常见面试问题预测
面试官可能会追问:
- 递归和迭代各自的优缺点是什么?
- 如何修改算法来记录满足条件的路径?
- 如果树非常大,你的算法会有什么问题?
- 如何优化空间复杂度?
5.3 复杂度分析要点
- 时间复杂度:每个节点访问一次,O(n)
- 空间复杂度:
- 递归:最坏O(n),平均O(logn)
- 迭代:取决于栈的大小,最坏O(n)
- 当树平衡时,空间复杂度可降为O(logn)
6. 实际工程中的应用场景
虽然看似是纯算法题,但路径求和的思想在实际工程中有广泛应用:
- 文件系统路径匹配:查找符合特定条件的文件路径
- 决策树分析:在机器学习中判断特征组合是否达到阈值
- 游戏AI设计:计算技能连招的伤害总值
- 金融风控:检测交易路径中的异常金额累加
在最近参与的一个电商促销系统开发中,我们就使用了类似的算法来检测用户优惠券使用路径是否满足特定条件。通过将优惠规则抽象为二叉树结构,可以高效判断用户是否满足叠加优惠条件。
7. 刷题训练建议
7.1 同类题目推荐
-
简单级别:
-
- 二叉树的最大深度
-
- 二叉树的最小深度
-
-
中级难度:
-
- 求根到叶子节点数字之和
-
- 路径总和III
-
-
高级难度:
-
- 二叉树中的最大路径和
-
- 监控二叉树
-
7.2 高效刷题方法
- 先独立思考15分钟,尝试自己写出解法
- 对比优秀题解,分析差距
- 手动模拟算法执行过程
- 记录错题本,标注错误原因
- 定期复习同类题目
7.3 调试技巧
当你的代码出现问题时:
- 打印递归过程中的中间变量
- 使用小规模测试用例手动验证
- 检查叶子节点判断条件是否正确
- 确认递归终止条件是否完备
- 验证负数节点的处理逻辑
我在最初练习这道题时,就曾因为忽略了负数节点而错误地添加了剪枝条件,导致部分用例无法通过。这个教训让我明白,即使看似可以优化的地方,也要先验证其正确性。