第六部分:深度优先搜索
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); }
}