前言
又是学习LeetCode二叉树部分内容的新一天,希望博主记录的内容能够对大家有所帮助 ,一起加油吧朋友们!💪💪💪
路径总和
LeetCode题目链接
题目的意思是在以root为根节点的二叉树中是否存在根节点到叶子节点的路径使得路径节点值相加等于目标和targetSum🤔🤔🤔
思路:递归处理的话,很明显该题需要回溯处理,因为递归到叶子节点时如果计算得到路径节点值相加(需要一个变量来记录)不等于给定的targetSum,则回溯往下一个路径处理,梳理完毕,我们接下来进行递归三要素的确定。🤔🤔🤔
递归三要素:
- 确定递归的返回值和参数
//首先的话判断是否存在所以递归函数返回值我们设为boolean
//其次的话我们要判断路径节点值求和是否等于targetSum(用targetSum去不断减去节点值即判断叶子节点时的targetSum是否为0)所以我们有个参数记录当前递归路径节点值的和
//当然也要传入以root为根节点的二叉树
boolean hasPathSum(TreeNode root, int targetSum){}
- 确定递归的出口
//因为是要找叶子节点,所以就不递归到空节点了
//找到叶子节点的时候进行值的判断,相等则返回true,否则返回false
if(root == null)return false; //如果本身是空节点
targetSum -= root.val;
if(root.left == null && root.right == null){
return targetSum == 0;
}
- 确定递归的单层处理逻辑
//如果不是叶子节点,则判断左节点所在路径节点值和是否等于目标值
if(root.left){
if(hasPathSum(root.left, targetSum))return true;
}
//左节点的路径值不等于目标值则往右节点计算
if(root.right){
if(hasPathSum(root.right, targetSum))return true;
}
return false;
接着我们来梳理迭代方法的处理逻辑吧,除了层序遍历用栈存储节点外还需要一个栈来记录对应节点到根节点的节点值的和🤔🤔🤔
/**迭代法 */
class Solution{
public boolean hasPathSum(TreeNode root, int targetSum){
if(root == null)return false; //提前结束
Stack<TreeNode> stackNode = new Stack<>();
Stack<Integer> stackSum = new Stack<>();
stackNode.push(root);
stackSum.push(root.val);
while(!stackNode.isEmpty()){
int size = stackNode.size();
for(int i = 0; i < size; i++){
TreeNode cur = stackNode.pop();
int sum = stackSum.pop();
//如果是叶子节点
if(cur.left == null && cur.right == null && sum == targetSum)return true;
//先压右节点进去,判断的时候就是左节点优先
if(cur.right != null){
stackNode.push(cur.right);
stackSum.push(sum + cur.right.val);
}
//再压左节点进去
if(cur.left != null){
stackNode.push(cur.left);
stackSum.push(sum + cur.left.val);
}
}
}
return false;
}
}
路径总和ii
这次是给二叉树的根节点root和一个targetSum,要求找出所有从根节点到叶子节点路径总和等于targetSum的路径。🤔🤔🤔
思路:首先就是递归到叶子节点(左右节点为null的节点),因为要返回路径,所以递归函数不需要返回值,而且要找出所有的路径即要遍历整个二叉树,梳理完毕,我们接下来进行递归三要素的确定。🤔🤔🤔
递归三要素:
- 确定递归函数的参数和返回值
//用一个变量result存储path,递归函数无需返回值
//传入targetSum(用来减去遍历的节点值,减去叶子节点的值为0时说明该路径可以被添加到result中)
//path为记录当前遍历的节点到根节点的路径
private List<List<Integer>> result = new ArrayList<>();
private void findPaths(TreeNode root, int targetSum, List<Integer> path){}
- 确定递归出口(这里因为path是传入的参数,所以在添加path的时候需要创建副本进行添加,否则回溯时会影响path)🤔🤔🤔
//递归出口
path.add(root.val);
if(root.left == null && root.right == null){ //遇到叶子节点
if(targetSum - root.val == 0){//找到和为targetSum的路径
result.add(new ArrayList<>(path)); //添加path副本!!!
}
return;//继续往下遍历
}
在 Java 中,new ArrayList<>(path) 是构造函数的一个调用,它的作用是创建一个新的ArrayList实例,并且用给定的集合(在这里是path)来初始化它。这意味着这个新创建的列表会包含path中的所有元素,但它是一个独立的对象,和原来的path没有任何关系。 🤔🤔🤔
//进一步解释添加副本的逻辑
List<Integer> path = new ArrayList<>(Arrays.asList(5, 4, 11));
List<Integer> temp = new ArrayList<>(path); // 创建了一个新列表 temp,内容为 [5, 4, 11]
- 确定单层处理逻辑
//单层逻辑处理
if(root.left != null){
findPaths(root.left, targetSum - root.val, path);//往左树递归
path.remove(path.size() - 1); //回溯移除刚才处理的节点
}
if(root.right != null){
findPaths(root.right, targetSum - root.val, path);//往右树递归
path.remove(path.size() - 1); //回溯移除刚才处理的节点
}
迭代法的话假如说用层序遍历,需要有记录节点、节点的路径以及剩余目标值的栈,会比较复杂,所以这里我们就不过多进行迭代法的学习了🤔🤔🤔
路径总和iii
给定一个二叉树的根节点root和一个整数targetSum,求二叉树里节点值和等于targetSum的路径的数目。这里的路径不需要从根节点开始但是必须是向下的路径。🤔🤔🤔
思路:需要记录一个节点的根节点到其的所有路径和的出现次数,可以使用哈希表,其中在节点遍历时我们更新一个currentSum(树根节点到当前节点的路径和)🤔🤔🤔,在访问当前节点的时把currentSum插入到哈希表中,当更新currentSum的时候需要检查有没有之前保存的路径和可以与当前的currentSum合并等于targetSum,有的话则说明找到了一条路径等于目标值。回溯的时候需要撤回之前的路径和记录🤔🤔🤔
递归三要素:
- 确定递归的参数和返回值
//因为有哈希表变量和传入的当前路径和currentPathSum,递归无返回值,所做的操作无非是更新currentPathSum和哈希表
private int result = 0;//存储目标路径树
private HashMap<Integer, Integer> prefixSumMap = new HashMap<>();//存储前续路径和
private void dfs(TreeNode root, int currentSum, int targetSum){}
- 确定递归的出口
//处理到叶子节点的时候仍需进行单层处理逻辑故为递归进空节点
if(root == null)return;
- 确定单层处理逻辑
//首先的话是更新当前路径和
currentSum += root.val;
//检查哈希表中是否存在前序路径和使得与当前路径和相加等于targetSum
if(prefixSumMap.containsKey(currentSum - targetSum)){
result += prefixSumMap.get(currentSum - targetSum);
}
//然后在哈希表中记录当前路径和的出现次数
prefixSumMap.put(currentSum, prefixSumMap.getOrDefault(currentSum, 0) + 1);
//递归左子树和右子树
dfs(root.left, currentSum, targetSum);
dfs(root.right, currentSum, targetSum);
//回溯
prefixSumMap.put(currenSum, prefixSumMap.get(currentSum) - 1);
逻辑处理梳理,这里其实最核心的处理逻辑就是一个哈希表检查的逻辑🤔🤔🤔
以及一个目标路径次数更新的逻辑🤔🤔🤔
递归完整代码如下:
/**递归法 */
class Solution {
private int result = 0;
private HashMap<Long, Integer> prefixSumMap = new HashMap<>();
public int pathSum(TreeNode root, int targetSum) {
// 初始条件:前缀和为0的路径出现一次
prefixSumMap.put(0L, 1);
dfs(root, 0L, targetSum);
return result;
}
private void dfs(TreeNode root, long currentSum, int targetSum) {
if (root == null) return;
// 更新当前路径的和
currentSum += root.val;
// 检查是否存在当前路径和 - targetSum
if (prefixSumMap.containsKey(currentSum - targetSum)) {
result += prefixSumMap.get(currentSum - targetSum);
}
// 在哈希表中记录当前路径和的出现次数
prefixSumMap.put(currentSum, prefixSumMap.getOrDefault(currentSum, 0) + 1);
// 递归左子树和右子树
dfs(root.left, currentSum, targetSum);
dfs(root.right, currentSum, targetSum);
// 回溯,撤消对当前路径和的记录
prefixSumMap.put(currentSum, prefixSumMap.get(currentSum) - 1);
}
}
迭代的话需要用栈来存储节点和当前的路径和,也比较复杂,这里博主就也先不码了,留到二轮复习是时候再啃一下🫡🫡🫡
总结
今天就到这里啦,明天继续加油👊👊👊