第六部分:深度优先搜索
112.路径总和(简单)
题目:给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 输出:true 解释:等于目标和的根节点到叶节点路径如上图所示。
示例 2:
输入:root = [1,2,3], targetSum = 5 输出:false 解释:树中存在两条根节点到叶子节点的路径: (1 --> 2): 和为 3 (1 --> 3): 和为 4 不存在 sum = 5 的根节点到叶子节点的路径。
示例 3:
输入:root = [], targetSum = 0 输出:false 解释:由于树是空的,所以不存在根节点到叶子节点的路径。
第一种思路:
空节点检查:
if (root == null) return false;
这行代码确保在递归过程中,如果当前节点为空,函数将返回
false
。这是基础的边界条件,防止在空节点上进行操作。叶子节点检查:
if (root.left == null && root.right == null) { return root.val == targetSum; }
这段代码检查当前节点是否为叶子节点(即没有左子节点和右子节点)。如果是叶子节点,函数将检查当前节点的值是否等于
targetSum
。如果相等,返回true
,表示找到了一个有效路径;否则返回false
。更新目标和并递归:
targetSum -= root.val;
这行代码更新
targetSum
,减去当前节点的值。这样做是为了在递归调用时,检查从根节点到当前节点的路径和是否能达到目标值。
return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
这行代码递归地检查左子树和右子树,返回两个子树中任意一个是否存在有效路径的结果。如果左子树或右子树中有路径和等于
targetSum
,则返回true
。一开始在下面的条件判断时,
// 如果当前节点为空,返回 false if (root == null) return false;
我添加了如下条件,但是如果再添加这些条件,会遗漏一些情况。
//第一种 if(root == null || root.val - targetSum > 0) return false; //这个条件会导致一些不必要的返回 false 的情况。比如,如果 root.val 是负数,而 targetSum 是正数,可能会导致错误的判断。这个条件并不是判断路径和是否可能的有效方式。 //第二种 if(root == null || Math.abs(root.val) > Math.abs(targetSum)) return false; //root.val - targetSum > 0 这个条件的意图是检查当前节点的值是否大于目标和。这会导致以下问题: //漏掉负数路径:如果树中存在负数节点,可能会导致路径和在某些情况下仍然可以达到 targetSum,但由于当前节点的值大于目标和而被错误地返回 false。 //不考虑路径的累积和:这个条件只比较当前节点的值与目标和,而没有考虑到从根节点到当前节点的路径和。路径和是由多个节点的值累积而成的,单独比较当前节点的值是不够的。
/**
* Definition for a binary tree node.
* 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 {
public boolean hasPathSum(TreeNode root, int targetSum) {
// 如果当前节点为空,返回 false
if (root == null) return false;
// 如果当前节点是叶子节点,检查路径和是否等于目标值
if (root.left == null && root.right == null) {
return root.val == targetSum;
}
// 递归检查左右子树,更新目标值
targetSum -= root.val;
return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
}
}
113.路径总和II(中等)
题目:给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22 输出:[[5,4,11,2],[5,8,4,5]]
示例 2:
输入:root = [1,2,3], targetSum = 5 输出:[]
第一种思路:
要找到从根节点到叶子节点的所有路径总和等于给定目标和的路径,需要在递归过程中维护当前路径,并在到达叶子节点时检查路径和是否等于目标和。此外,还需要在返回时移除当前节点的值,以便在回溯时不会影响其他路径。
这种方法有效地使用深度优先搜索(DFS)来查找所有有效路径,同时通过回溯确保探索所有潜在路径。
初始化:
pathSum
方法初始化了两个列表:list
用于存储所有有效路径,tempList
用于跟踪当前正在探索的路径。递归探索:
findPaths
方法是一个递归函数,用于探索二叉树中的每个节点:
如果当前节点为
null
,则直接返回(基本情况)。将当前节点的值添加到
tempList
中。叶子节点检查:如果当前节点是叶子节点(左右子节点均为
null
)并且其值等于剩余的targetSum
,则表示找到了一条有效路径。将tempList
的副本添加到list
中。继续搜索:如果当前节点不是叶子节点,函数会递归调用自身,分别处理左子节点和右子节点,同时调整
targetSum
,减去当前节点的值。回溯:在探索完两个子节点后,函数通过从
tempList
中移除最后添加的节点值来进行回溯。这一步是至关重要的,因为它允许函数在不保留先前路径值的情况下探索新路径。
class Solution {
// 主方法,用于查找所有路径,使其节点值之和等于 targetSum
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
// 存储所有有效路径的列表
List<List<Integer>> list = new ArrayList<>();
// 临时列表,用于存储当前路径
List<Integer> tempList = new ArrayList<>();
// 开始递归查找路径
findPaths(root, targetSum, tempList, list);
return list;
}
// 辅助方法,执行回溯
private void findPaths(TreeNode node, int targetSum, List<Integer> tempList, List<List<Integer>> list) {
// 基本情况:如果当前节点为 null,返回
if (node == null) return;
// 将当前节点的值添加到路径中
tempList.add(node.val);
// 检查是否为叶子节点,并且路径和等于 targetSum
if (node.left == null && node.right == null && node.val == targetSum) {
// 如果路径和匹配,将当前路径的副本添加到列表中
list.add(new ArrayList<>(tempList));
} else {
// 继续探索路径,更新 targetSum
// 从 targetSum 中减去当前节点的值,探索左子树
findPaths(node.left, targetSum - node.val, tempList, list);
// 探索右子树
findPaths(node.right, targetSum - node.val, tempList, list);
}
// 回溯:从路径中移除当前节点的值
tempList.remove(tempList.size() - 1);
}
}