1. 问题背景与需求分析
- 路径总和是LeetCode上一道经典的二叉树遍历问题。给定一个二叉树和一个目标和,要求判断该树中是否存在从根节点到叶子节点的路径,使得路径上所有节点值相加等于目标和。
这道题在2023年LeetCode周赛430中作为中等难度题目出现,考察的核心是对深度优先搜索(DFS)算法的理解和应用。根据统计,该题在热门100题中排名靠前,是面试中高频出现的二叉树问题之一。
提示:虽然题目描述简单,但实际解题时容易忽略"到叶子节点"这个关键条件,很多同学会在非叶子节点就提前返回结果,这是最常见的错误之一。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 深度优先搜索算法原理
2.1 DFS基础概念
深度优先搜索是一种用于遍历或搜索树或图的算法。其核心思想是尽可能深地探索树的分支,当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。
在二叉树场景下,DFS通常有三种实现方式:
- 前序遍历:根→左→右
- 中序遍历:左→根→右
- 后序遍历:左→右→根
对于路径总和问题,前序遍历是最自然的选择,因为我们需要从根节点开始累加路径值。
2.2 DFS与BFS的对比
与广度优先搜索(BFS)相比,DFS有以下特点:
- 内存消耗较小(栈深度通常小于BFS队列大小)
- 更适合寻找所有解的问题
- 实现更简洁(递归形式)
- 可能找到的不是最短路径
在路径总和问题中,我们只需要判断是否存在符合条件的路径,而不关心路径长度,因此DFS更为合适。
3. 解题思路与算法设计
3.1 递归解法
递归是实现DFS最直观的方式。基本思路是:
- 从根节点开始,每次用目标和减去当前节点值
- 如果当前节点是叶子节点,判断剩余和是否为0
- 递归检查左右子树
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)(树退化为链表时递归栈深度)
3.2 迭代解法
使用栈模拟递归过程,避免递归的系统开销:
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
这里使用后进先出的栈,保证深度优先的搜索顺序。注意右子节点先入栈,因为栈是后进先出,这样左子节点会先被处理。
4. 边界条件与常见错误
4.1 空树处理
当输入为空树时,无论目标和是多少都应返回False。这是很多同学容易忽略的边界情况。
4.2 负数节点值
题目没有限制节点值必须为正数,因此需要考虑节点值为负的情况。这时路径上的累加和可能会出现先增后减的情况,但我们的算法已经天然处理了这种情况。
4.3 大数溢出
在Python中不需要考虑整数溢出问题,但在Java/C++等语言中,如果节点值很大,多次累加可能导致溢出。这时可以考虑用减法代替加法(如我们的解法所示)。
5. 算法优化与变种
5.1 提前终止
当找到一条符合条件的路径时,可以立即返回,不需要继续搜索其他路径。这在递归和迭代解法中都已实现。
5.2 返回所有路径
类似题目113要求返回所有符合条件的路径。只需稍作修改,在找到路径时记录而非直接返回:
python复制def pathSum(root, targetSum):
res = []
def dfs(node, path, remain):
if not node:
return
if not node.left and not node.right and remain == node.val:
res.append(path + [node.val])
return
dfs(node.left, path + [node.val], remain - node.val)
dfs(node.right, path + [node.val], remain - node.val)
dfs(root, [], targetSum)
return res
5.3 路径方向不限
更复杂的变种是路径不一定从根到叶,也不一定自上而下。这时需要双重DFS或前缀和技巧,难度会显著增加。
6. 实际应用场景
DFS在二叉树问题中的应用非常广泛,除了路径总和外,还包括:
- 二叉树序列化/反序列化
- 最近公共祖先(LCA)问题
- 二叉树直径计算
- 二叉搜索树验证
在工程实践中,DFS常用于:
- 文件系统遍历
- 依赖关系解析
- 游戏树搜索
- 网络爬虫的URL遍历
7. 个人解题心得
在多次解答这类题目后,我总结了几个关键点:
- 明确终止条件:在本题中必须到达叶子节点才算完整路径
- 参数传递方式:使用减法比传递累加和更直观
- 递归转迭代:栈中需要同时保存节点和当前剩余和
- 测试用例设计:要包含单节点树、只有左/右子树、负数节点值等情况
一个容易忽略的细节是Python中布尔值的短路求值特性。在递归解法中我们用or连接两个递归调用,这样当左子树找到路径时就不会再搜索右子树,提高了效率。
对于面试场景,建议先写出递归解法,再讨论是否可以改为迭代实现,并分析时间/空间复杂度。同时要主动考虑边界条件和可能的优化点,展示全面的思考过程。
