当前位置: 首页 > news >正文

112. 路径总和

目录

题目链接:

题目:

解题思路:

代码:

递归法

迭代法

总结:


题目链接:

112. 路径总和 - 力扣(LeetCode)

题目:

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

叶子节点 是指没有子节点的节点。

解题思路:

递归回溯法:使用前序遍历,(树的递归何时需要返回值,如果是找一条路径,需要返回值;如果是需要处理整颗树且不用处理返回值,则不需要),判断条件是遇到节点恰好值为目标值即可返回true;

迭代法也是可以的,但是需要俩栈,一个存储当前节点,一直存储当前值,判断是否需要返回

代码:

递归法

/*** 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) {if(root==null){return false;}return find(root,targetSum);        }public boolean find(TreeNode root,int sum){if(root==null) return false;sum-=root.val;if(root.left==null&&root.right==null){return sum==0;}if(root.left!=null){boolean left=find(root.left,sum);if(left==true){return true;}}if(root.right!=null){boolean right=find(root.right,sum);if(right==true){return true;}}return false;}
}

二叉树路径总和判断算法解析:深度优先搜索的递归实现
在二叉树的算法问题中,判断是否是否存在一条从根节点到叶子节点的路径,使得路径上所有节点值之和等于目标值,是一道经典的基础题目。这道题不仅考察了对二叉树结构的理解,也考验了递归遍历算法的应用。本文将详细解析一段基于深度优先搜索(DFS)的递归实现代码,从问题分析到代码执行流程,全面剖析这一问题的解决方案。
问题背景与定义解析
首先,我们需要明确问题的定义:

给定一棵二叉树和一个目标和targetSum
判断该树中是否存在一条从根节点到叶子节点的路径
路径上所有节点的值相加等于targetSum
叶子节点是指没有左子树和右子树的节点(即left == null && right == null)

例如,在下面的二叉树中,目标和为 22:

plaintext
      5
     / \
    4   8
   /   / \
  11  13  4
 /  \      \
7    2      1

存在一条路径5->4->11->2,节点值之和为 5+4+11+2=22,因此算法应返回true。

理解 "从根到叶子" 这一条件至关重要,很多初学者会误将任意路径(如非叶子节点结束的路径)考虑在内,这是需要特别注意的。
代码整体结构分析
这段代码采用递归深度优先搜索的思路,通过两个方法协作完成判断:

java
运行
class Solution {
    // 主方法:判断是否存在符合条件的路径
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root==null){
            return false;
        }
        return find(root,targetSum);        
    }
    
    // 辅助递归方法:搜索路径
    public boolean find(TreeNode root,int sum){
        // 递归终止条件
        if(root==null) return false;
        
        // 减去当前节点值
        sum -= root.val;
        
        // 判断是否为叶子节点且sum已减为0
        if(root.left==null&&root.right==null){
            return sum==0;
        }
        
        // 递归搜索左子树
        if(root.left!=null){
            boolean left=find(root.left,sum);
            if(left==true){
                return true;
            }
        }
        
        // 递归搜索右子树
        if(root.right!=null){
            boolean right=find(root.right,sum);
            if(right==true){
                return true;
            }
        }
        
        // 左右子树都没有符合条件的路径
        return false;
    }
}

代码的整体结构遵循了 "主方法 + 辅助递归方法" 的模式:

主方法hasPathSum负责处理空树的边界情况,并启动递归搜索
辅助方法find实现核心的递归逻辑,包括:
递归终止条件判断
当前节点值处理
叶子节点判断与目标和校验
左子树和右子树的递归搜索
结果合并与返回

这种结构将边界处理与核心逻辑分离,使代码更清晰易读。
核心代码逐行解析
1. 主方法:边界条件处理
java
运行
public boolean hasPathSum(TreeNode root, int targetSum) {
    if(root==null){
        return false;
    }
    return find(root,targetSum);        
}

主方法的逻辑非常简洁:

首先判断根节点是否为null(空树),如果是,直接返回false,因为空树不可能有任何路径
如果根节点不为null,调用辅助方法find开始递归搜索,传入根节点和目标和

这一步处理了空树的特殊情况,避免了后续递归中可能出现的空指针异常。
2. 辅助方法:递归终止条件
java
运行
public boolean find(TreeNode root,int sum){
    if(root==null) return false;
    // ... 其他逻辑
}

这是递归的基本终止条件:当传入的节点为null时,返回false。这意味着当前路径无法继续延伸到有效节点,自然不可能形成符合条件的路径。
3. 当前节点值处理
java
运行
sum -= root.val;

这行代码是算法的核心操作之一:将当前节点的值从剩余和中减去。通过这种方式,我们追踪从根节点到当前节点的路径上已累积的节点值之和。当到达叶子节点时,如果剩余的sum为 0,说明这条路径的总和等于初始的目标和。

这种 "减法" 思路比 "加法" 思路(累加路径和再与目标和比较)更简洁,避免了额外的累加变量。
4. 叶子节点判断与结果校验
java
运行
if(root.left==null&&root.right==null){
    return sum==0;
}

这是判断路径是否有效的关键条件:

首先检查当前节点是否为叶子节点(左右子节点都为null)
如果是叶子节点,检查经过该节点后剩余的sum是否为 0
如果sum == 0,返回true(找到符合条件的路径)
否则,返回false(该路径不符合条件)

这一步确保了只有 "从根到叶子" 的完整路径才会被考虑,符合问题的定义。
5. 左子树递归搜索
java
运行
if(root.left!=null){
    boolean left=find(root.left,sum);
    if(left==true){
        return true;
    }
}

这段代码处理左子树的递归搜索:

首先检查左子节点是否存在(root.left != null)
如果存在,递归调用find方法搜索左子树,传入左子节点和当前剩余的sum
接收递归返回的结果,如果为true(左子树中存在符合条件的路径),立即返回true

这种 "短路" 处理非常高效,一旦找到一条符合条件的路径,就可以立即返回,无需继续搜索其他路径。
6. 右子树递归搜索
java
运行
if(root.right!=null){
    boolean right=find(root.right,sum);
    if(right==true){
        return true;
    }
}

这段代码与左子树处理逻辑类似:

检查右子节点是否存在
递归搜索右子树
如果找到符合条件的路径,立即返回true

注意这里先搜索左子树,再搜索右子树,体现了深度优先搜索的特点。
7. 无符合条件路径的返回
java
运行
return false;

如果当前节点的左右子树都搜索完毕且没有找到符合条件的路径,返回false,表示从当前节点出发的所有路径都不符合条件。
算法执行流程示例
为了更直观地理解算法的执行过程,我们以上面提到的二叉树为例,目标和为 22:

plaintext
      5
     / \
    4   8
   /   / \
  11  13  4
 /  \      \
7    2      1

执行步骤分解:

初始调用 hasPathSum(5, 22)
根节点不为 null,调用 find(5, 22)
执行 find(5, 22)
5 不为 null
sum = 22 - 5 = 17
5 不是叶子节点(有左右子树)
左子树存在,调用 find(4, 17)
执行 find(4, 17)
4 不为 null
sum = 17 - 4 = 13
4 不是叶子节点(有左子树)
左子树存在,调用 find(11, 13)
执行 find(11, 13)
11 不为 null
sum = 13 - 11 = 2
11 不是叶子节点(有左右子树)
左子树存在,调用 find(7, 2)
执行 find(7, 2)
7 不为 null
sum = 2 - 7 = -5
7 是叶子节点(左右子树都为 null)
sum != 0,返回false
回到 find(11, 13) 的左子树处理
左子树搜索返回false,继续处理右子树
右子树存在,调用 find(2, 2)
执行 find(2, 2)
2 不为 null
sum = 2 - 2 = 0
2 是叶子节点
sum == 0,返回true
回到 find(11, 13) 的右子树处理
右子树搜索返回true,立即返回true
回到 find(4, 17) 的左子树处理
左子树搜索返回true,立即返回true
回到 find(5, 22) 的左子树处理
左子树搜索返回true,立即返回true
回到 hasPathSum 方法,返回true

整个过程清晰地展示了递归如何沿着路径5->4->11->2进行深度优先搜索,并在找到符合条件的路径后立即返回结果,避免了不必要的搜索。
算法复杂度分析
时间复杂度
在最坏情况下,算法需要遍历二叉树的所有节点(例如,当不存在符合条件的路径时)
对于包含 n 个节点的二叉树,时间复杂度为 O (n)
在最好情况下(例如,根节点到左叶子的路径就符合条件),时间复杂度为 O (1)(仅需访问几个节点)
空间复杂度
空间复杂度取决于递归调用栈的深度
在最坏情况下(斜树),递归深度为 n,空间复杂度为 O (n)
在最好情况下(平衡二叉树),递归深度为 log (n),空间复杂度为 O (log n)
此外,算法没有使用额外的辅助数据结构,空间效率较高
与迭代法(DFS)的对比
除了递归实现,我们还可以使用迭代法(基于栈)实现深度优先搜索:

java
运行
public boolean hasPathSum(TreeNode root, int targetSum) {
    if (root == null) return false;
    
    // 使用栈存储节点和当前路径的剩余和
    Stack<Pair<TreeNode, Integer>> stack = new Stack<>();
    stack.push(new Pair<>(root, targetSum - root.val));
    
    while (!stack.isEmpty()) {
        Pair<TreeNode, Integer> pair = stack.pop();
        TreeNode node = pair.getKey();
        int sum = pair.getValue();
        
        // 检查是否为叶子节点且剩余和为0
        if (node.left == null && node.right == null && sum == 0) {
            return true;
        }
        
        // 右子节点入栈
        if (node.right != null) {
            stack.push(new Pair<>(node.right, sum - node.right.val));
        }
        
        // 左子节点入栈
        if (node.left != null) {
            stack.push(new Pair<>(node.left, sum - node.left.val));
        }
    }
    
    return false;
}

两种实现方式的对比:

特性    递归实现    迭代实现
代码复杂度    低,逻辑清晰    较高,需要手动管理栈
空间复杂度    O (h),h 为树高    O (h),h 为树高
适用场景    树高较小时    树高较大时(避免栈溢出)
可读性    高,符合递归思维    较低,需要理解栈操作
性能    有函数调用开销    无函数调用开销,稍优

在实际开发中,递归实现更简洁直观,适合大多数情况;迭代实现则在处理极深的树时更可靠,不会出现栈溢出异常。
代码优化建议
这段代码的逻辑已经非常清晰,但可以从以下几个方面进行优化,提高可读性和效率:
1. 合并方法,减少代码量
可以将辅助方法find的逻辑合并到主方法中,减少方法调用开销:

java
运行
public boolean hasPathSum(TreeNode root, int targetSum) {
    if (root == null) return false;
    
    targetSum -= root.val;
    
    // 叶子节点判断
    if (root.left == null && root.right == null) {
        return targetSum == 0;
    }
    
    // 递归搜索左右子树
    return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
}

这个版本更为简洁,利用了逻辑或(||)的短路特性:如果左子树搜索返回true,则不会执行右子树搜索。
2. 避免不必要的 null 检查
原代码中对左右子节点的null检查可以省略,因为递归方法内部已经处理了root == null的情况:

java
运行
public boolean find(TreeNode root, int sum) {
    if (root == null) return false;
    
    sum -= root.val;
    
    if (root.left == null && root.right == null) {
        return sum == 0;
    }
    
    // 无需检查子节点是否为null,递归内部会处理
    return find(root.left, sum) || find(root.right, sum);
}

这种方式减少了代码量,且逻辑更为紧凑。
3. 变量名优化
将sum改为remainingSum(剩余和)可以使代码意图更清晰:

java
运行
public boolean find(TreeNode root, int remainingSum) {
    if (root == null) return false;
    
    remainingSum -= root.val;
    
    if (root.left == null && root.right == null) {
        return remainingSum == 0;
    }
    
    return find(root.left, remainingSum) || find(root.right, remainingSum);
}

良好的变量名可以提高代码的可维护性,使其他开发者更容易理解代码逻辑。
常见错误与边界情况
在实现路径总和判断算法时,有一些常见的错误和边界情况需要注意:
1. 空树情况
当输入的根节点为null时,算法应返回false,原代码正确处理了这种情况。
2. 只有根节点的树
如果树中只有根节点,需要判断根节点的值是否等于目标和,且根节点是叶子节点:

plaintext
    5

当目标和为 5 时,应返回true;否则返回false。
3. 目标和为负数的情况
算法应能正确处理目标和为负数的情况,例如:

plaintext
    -2
     \
      -3

目标和为 - 5 时,路径-2->-3的和为 - 5,应返回true。
4. 节点值有负数的情况
当树中存在负数节点时,算法也应能正确判断,例如:

plaintext
    1
   / \
  -2  3

目标和为 - 1 时,路径1->-2的和为 - 1,应返回true。
常见错误实现
初学者常犯的错误包括:

没有检查节点是否为叶子节点,只要路径和等于目标和就返回true
累加路径和时出现计算错误
递归终止条件处理不当,导致空指针异常
没有利用短路特性,继续搜索已找到符合条件的路径
总结与思考
本文详细解析了基于递归深度优先搜索的二叉树路径总和判断算法,从代码结构到执行流程,再到复杂度分析和优化建议,全面展示了这一问题的解决方案。

这个算法的核心思想是:

利用递归进行深度优先搜索,遍历从根节点到叶子节点的所有可能路径
通过减去当前节点值的方式,追踪剩余需要满足的和
在叶子节点处检查剩余和是否为 0,以判断路径是否符合条件
利用短路特性,一旦找到符合条件的路径就立即返回

通过这个问题,我们可以学到:

递归在树结构遍历中的灵活应用
深度优先搜索的典型实现方式
路径问题的一般解决思路(追踪路径累积值)
边界条件处理的重要性

对于初学者来说,建议多动手模拟递归的执行过程,理解递归调用栈的变化和变量值的传递。同时,尝试用不同的方法(递归和迭代)解决同一问题,可以加深对算法思想的理解。

最后,这一问题的解决方案体现了 "减而治之" 的算法设计思想:将大问题(判断从根到叶子的路径和)分解为小问题(判断从当前节点到叶子的剩余和),通过解决小问题来最终解决大问题。这种思想在很多树和图的算法问题中都有广泛应用,掌握它对于提升算法设计能力非常有帮助。

迭代法

/*** 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;*     }* }*//*
*Stack Integer ArrayList String StringBuffer peek
*Collections imports LinkedList offer return
*empty polls offerLast pollFirst isEmpty
*List Deque append
*/
class Solution {public boolean hasPathSum(TreeNode root, int targetSum) {if(root==null) return false;Stack<TreeNode> jiedian=new Stack<>();Stack<Integer> zhi=new Stack<>();jiedian.push(root);zhi.push(root.val);while(!jiedian.isEmpty()){int size=jiedian.size();for(int i=0;i<size;i++){TreeNode node=jiedian.pop();int val=zhi.pop();if(node.left==null&&node.right==null&&val==targetSum){return true;}if(node.right!=null){jiedian.push(node.right);zhi.push(node.right.val+val);}if(node.left!=null){jiedian.push(node.left);zhi.push(node.left.val+val);}}}return false;}
}

总结:

本文介绍了LeetCode 112题“路径总和”的两种解法:递归法和迭代法。递归法通过深度优先搜索(DFS)遍历二叉树,在叶子节点检查剩余和是否为零;迭代法则使用栈模拟递归过程。两种方法的时间复杂度均为O(n),空间复杂度取决于树的高度。文章详细解析了代码逻辑、执行流程和复杂度分析,并提供了优化建议和常见错误示例,帮助读者深入理解这一经典二叉树问题。


文章转载自:

http://u7ZnORLL.rqwwm.cn
http://8AOah9i5.rqwwm.cn
http://UhBxF2Kl.rqwwm.cn
http://ZV2Emmcv.rqwwm.cn
http://JjI0NtOG.rqwwm.cn
http://XdgzPvEr.rqwwm.cn
http://0i2yvRuq.rqwwm.cn
http://5uVWsKhY.rqwwm.cn
http://2C0FousG.rqwwm.cn
http://Jgq2GYWp.rqwwm.cn
http://SxHd4BNL.rqwwm.cn
http://Oaxy6kTd.rqwwm.cn
http://fNKu7saI.rqwwm.cn
http://o4FF9gis.rqwwm.cn
http://pqWP3Gcf.rqwwm.cn
http://7HffRe79.rqwwm.cn
http://sMLEI8Di.rqwwm.cn
http://5lvziPXK.rqwwm.cn
http://dRthQoD9.rqwwm.cn
http://cOYU5Wqd.rqwwm.cn
http://Au0sXcvQ.rqwwm.cn
http://YC9muPYH.rqwwm.cn
http://OtrpwQKA.rqwwm.cn
http://RNWVmFju.rqwwm.cn
http://KSWc8ETH.rqwwm.cn
http://8i7G5P0I.rqwwm.cn
http://Lh9TszKM.rqwwm.cn
http://NlJLizT4.rqwwm.cn
http://tMb5PMxJ.rqwwm.cn
http://pK0h3EYV.rqwwm.cn
http://www.dtcms.com/a/378815.html

相关文章:

  • 四,基础开发工具(下)
  • Docker+jenkinsPipeline 运行实现python自动化测试
  • Android图案解锁绘制
  • 分布式事务性能优化:从故障现场到方案落地的实战手记(一)
  • JVM第一部分
  • websocket和socket io的区别
  • codebuddy ai cli安装教程
  • MySQL5.7.44保姆级安装教程
  • 正则表达式基础
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘pandas-profiling’问题
  • GRPOConfig中参数num_generations
  • 电源线束选型
  • 系统稳定性保障:研发规约V1.0
  • Day13 | Java多态详解
  • hbuilderx配置微信小程序开发环境
  • opc ua c#订阅报错【记录】
  • Caffeine 本地缓存最佳实践与性能优化指南
  • MySQL 高级特性与性能优化:深入理解函数、视图、存储过程、触发器
  • Java常见排序算法实现
  • 生产环境禁用AI框架工具回调:安全风险与最佳实践
  • Git - Difftool
  • leetcode28( 汇总区间)
  • 直击3D内容创作痛点-火山引擎多媒体实验室首次主持SIGGRAPH Workshop,用前沿技术降低沉浸式内容生成门槛
  • 鸿蒙next kit 卡片引入在线|本地图片注意事项
  • 学习番外:Docker和K8S理解
  • Leetcode 刷题记录 21 —— 技巧
  • 卷积神经网络CNN-part5-NiN
  • 散斑深度相机原理
  • 中元的星问
  • 使用 NumPy 读取平面点集并分离列数据