力扣解题-112. 路径总和

0 阅读7分钟

力扣解题-112. 路径总和

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

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

示例 1:

image.png

输入: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

解释:由于树是空的,所以不存在根节点到叶子节点的路径。

提示:

树中节点的数目在范围 [0, 5000] 内

-1000 <= Node.val <= 1000

-1000 <= targetSum <= 1000

Related Topics

树、深度优先搜索、广度优先搜索、二叉树


第一次解答

解题思路

核心方法:深度优先搜索(DFS)递归法,通过递归遍历二叉树的每条根到叶子的路径,逐步减去当前节点值,最终在叶子节点处校验剩余目标和是否等于节点值,逻辑简洁且符合二叉树的递归特性,是本题的基础最优解法。

核心逻辑拆解

判断路径总和的核心是“路径遍历+剩余和校验”,关键在于明确递归的终止条件和状态传递:

  1. 空树处理:若根节点root == null,直接返回false(空树无任何路径);
  2. 递归函数核心逻辑
    • 递归终止条件1:当前节点root == null,返回false(路径未到叶子节点就结束,无效);
    • 递归终止条件2:当前节点是叶子节点(root.left == null && root.right == null),校验targetSum == root.val(剩余目标和等于当前叶子节点值,说明路径和匹配);
    • 状态传递:递归遍历左、右子树,将目标和减去当前节点值(targetSum - root.val)作为新的目标和传递下去;
    • 结果合并:只要左子树或右子树有任意一条路径满足条件,就返回true(逻辑或||)。
具体步骤(以示例1 root=[5,4,8,11,null,13,4,7,2,null,null,null,1]、targetSum=22为例)
递归层级当前节点剩余目标和是否叶子节点操作结果
1522递归左子树(4),剩余17-
2417递归左子树(11),剩余13-
31113递归左子树(7)剩余2;递归右子树(2)剩余1-
4722≠7 → 返回false-
4211≠2 → 返回false?(修正:实际剩余和计算为22-5-4-11=2,2==2 → 返回true)true
31113右子树返回true → 整体返回truetrue
最终递归回溯到根节点,返回true,与示例一致。
关键计算过程(示例1正确路径:5→4→11→2)
  • 根节点5:剩余和=22-5=17;
  • 节点4:剩余和=17-4=13;
  • 节点11:剩余和=13-11=2;
  • 叶子节点2:剩余和=2,等于节点值2 → 路径匹配,返回true。
性能说明
  • 时间复杂度:O(n)(每个节点仅被访问一次,n为节点总数);
  • 空间复杂度:O(h)(h为树的高度,递归栈深度等于树的高度):
    • 最好情况(平衡二叉树):h = log₂n,空间复杂度O(logn);
    • 最坏情况(斜树):h = n,空间复杂度O(n);
  • 优势:
    1. 递归逻辑贴合路径遍历的直觉,易理解、易实现;
    2. 短路特性:只要找到一条匹配路径,递归会立即终止(逻辑或||的短路求值),无需遍历所有节点;
    3. 天然处理空树、单节点树等边界场景。
    public boolean hasPathSum(TreeNode root, int targetSum) {
        boolean result=false;
        if(root!=null){
            return dfs(root,targetSum);
        }else {
            return false;
        }
    }

    public boolean dfs(TreeNode root,int targetSum){
        if(root==null){
            return false;
        }
        //说明是叶子节点,判断是否为0
        if(root.left==null && root.right==null){
            if(targetSum==root.val){
                return true;
            }else {
                return false;
            }
        }
        return dfs(root.left, targetSum - root.val) || dfs(root.right, targetSum - root.val);
    }

示例解答

解题思路

解法1:广度优先搜索(BFS)迭代法(非递归)

核心方法:队列辅助层级遍历,使用两个队列分别存储待遍历的节点和对应的剩余目标和,逐层遍历二叉树,在叶子节点处校验剩余和是否匹配,避免递归栈开销,适合树高度较大的场景。

代码实现
import java.util.LinkedList;
import java.util.Queue;

public boolean hasPathSum(TreeNode root, int targetSum) {
    if (root == null) {
        return false;
    }
    // 队列1:存储待遍历的节点
    Queue<TreeNode> nodeQueue = new LinkedList<>();
    // 队列2:存储对应节点的剩余目标和
    Queue<Integer> sumQueue = new LinkedList<>();
    
    nodeQueue.offer(root);
    sumQueue.offer(targetSum);
    
    while (!nodeQueue.isEmpty()) {
        TreeNode currNode = nodeQueue.poll();
        int currSum = sumQueue.poll();
        
        // 叶子节点校验
        if (currNode.left == null && currNode.right == null) {
            if (currSum == currNode.val) {
                return true;
            }
            continue; // 非匹配叶子节点,跳过后续处理
        }
        
        // 左子节点入队
        if (currNode.left != null) {
            nodeQueue.offer(currNode.left);
            sumQueue.offer(currSum - currNode.val);
        }
        // 右子节点入队
        if (currNode.right != null) {
            nodeQueue.offer(currNode.right);
            sumQueue.offer(currSum - currNode.val);
        }
    }
    // 遍历完所有节点未找到匹配路径
    return false;
}
核心逻辑说明
  1. 初始化:根节点和初始目标和分别入队;
  2. 层级遍历
    • 取出队首节点和对应的剩余目标和;
    • 若为叶子节点,校验剩余和是否等于节点值,匹配则直接返回true
    • 非叶子节点则将左右子节点入队,剩余和更新为currSum - currNode.val
  3. 终止条件:队列空(遍历完所有节点)则返回false
性能说明
  • 时间复杂度:O(n)(每个节点仅入队/出队一次);
  • 空间复杂度:O(n)(最坏情况队列存储一层所有节点,如完全二叉树最后一层有n/2个节点);
  • 优势:
    1. 非递归实现,避免递归栈溢出风险(如斜树场景);
    2. 层级遍历逻辑清晰,可直观看到每一层的剩余和状态;
  • 劣势:需要额外的队列空间,空间效率略低于递归法。
解法2:递归代码优化(精简版)

核心方法:在原递归逻辑基础上精简代码,合并冗余判断,保持逻辑等价但更简洁。

代码实现
public boolean hasPathSum(TreeNode root, int targetSum) {
    // 空树直接返回false
    if (root == null) {
        return false;
    }
    // 叶子节点校验
    if (root.left == null && root.right == null) {
        return targetSum == root.val;
    }
    // 递归遍历左右子树,传递剩余和
    return hasPathSum(root.left, targetSum - root.val) 
        || hasPathSum(root.right, targetSum - root.val);
}
优势说明
  • 逻辑等价:与原递归解法完全一致,仅精简了冗余的变量和判断;
  • 代码更简洁:去掉了单独的dfs函数,直接在主函数递归,减少代码量;
  • 可读性更高:核心逻辑一步到位,新手易理解。
解法3:迭代版DFS(栈模拟)

核心方法:栈辅助深度优先遍历,使用栈存储节点和对应的剩余目标和,模拟递归的DFS过程,非递归实现且保持与递归法相同的遍历顺序。

代码实现
import java.util.Stack;

public boolean hasPathSum(TreeNode root, int targetSum) {
    if (root == null) {
        return false;
    }
    // 栈:存储节点和对应的剩余目标和(使用数组或自定义类,此处用两个栈)
    Stack<TreeNode> nodeStack = new Stack<>();
    Stack<Integer> sumStack = new Stack<>();
    
    nodeStack.push(root);
    sumStack.push(targetSum);
    
    while (!nodeStack.isEmpty()) {
        TreeNode currNode = nodeStack.pop();
        int currSum = sumStack.pop();
        
        // 叶子节点校验
        if (currNode.left == null && currNode.right == null) {
            if (currSum == currNode.val) {
                return true;
            }
            continue;
        }
        
        // 栈后进先出,先压右子树,后压左子树(保证DFS顺序)
        if (currNode.right != null) {
            nodeStack.push(currNode.right);
            sumStack.push(currSum - currNode.val);
        }
        if (currNode.left != null) {
            nodeStack.push(currNode.left);
            sumStack.push(currSum - currNode.val);
        }
    }
    return false;
}
核心逻辑说明
  1. 栈初始化:根节点和初始目标和入栈;
  2. DFS遍历
    • 弹出栈顶节点和剩余和,校验是否为叶子节点;
    • 非叶子节点则先压右子树、后压左子树(栈后进先出,保证先遍历左子树);
  3. 终止条件:找到匹配路径返回true,否则遍历完返回false
性能说明
  • 时间复杂度:O(n)(与递归法一致);
  • 空间复杂度:O(h)(栈深度等于树的高度,与递归法一致);
  • 优势:非递归实现,避免栈溢出,且空间效率与递归法相同。

总结

  1. DFS递归法(第一次解答):O(n)时间+O(h)空间,逻辑简洁、易理解,是本题的基础最优解法;
  2. BFS迭代法:O(n)时间+O(n)空间,非递归、层级遍历,适合树高度较大的场景;
  3. 递归精简版:代码更简洁,逻辑等价,可读性更高;
  4. DFS迭代版(栈模拟):O(n)时间+O(h)空间,非递归且空间效率与递归法一致;
  5. 关键技巧
    • 核心思想:路径总和的本质是“根到叶子的路径遍历+剩余和校验”;
    • 终止条件:必须在叶子节点处校验剩余和,非叶子节点的空节点无意义;
    • 短路优化:递归/迭代中使用逻辑或||,找到匹配路径后立即返回,无需遍历所有节点。