【LeetCode】112. 路径总和

leetcode

 

解题思路

要判断二叉树中是否存在根节点到叶子节点的路径和等于目标值 targetSum,可以通过 ​递归深度优先搜索(DFS)​ 或 ​迭代广度优先搜索(BFS)​ 实现。以下是两种方法的实现原理:


方法一:递归法(DFS)​

核心思想
从根节点开始,每次递归将 targetSum 减去当前节点的值。当到达叶子节点时,若剩余 targetSum 等于叶子节点的值,则存在满足条件的路径。 

代码实现
type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

func hasPathSum(root *TreeNode, targetSum int) bool {
    if root == nil {
        return false  // 空树直接返回false[3,5](@ref)
    }
    // 如果是叶子节点,检查当前值是否等于剩余targetSum
    if root.Left == nil && root.Right == nil {
        return root.Val == targetSum
    }
    // 递归检查左右子树,传递更新后的targetSum
    return hasPathSum(root.Left, targetSum - root.Val) || 
           hasPathSum(root.Right, targetSum - root.Val)
}

关键点解析

  1. 空树处理:若根节点为空(如示例3),直接返回 false。
  2. 叶子节点判断:当左右子节点均为空时,检查当前节点值是否等于 targetSum。
  3. 递归传递:非叶子节点需递归左右子树,并将 targetSum 减去当前节点的值。

复杂度分析

  • 时间复杂度:O(n),每个节点访问一次。
  • 空间复杂度:O(h),递归栈深度(h为树高度,最坏情况为链状树,复杂度O(n))。

方法二:迭代法(BFS)​

核心思想
使用队列存储节点及其路径和。每次处理节点时,若为叶子节点且路径和等于 targetSum,则返回 true;否则将子节点及其累积和加入队列。 

代码实现
func hasPathSum(root *TreeNode, targetSum int) bool {
    if root == nil {
        return false
    }
    // 队列存储节点及当前路径和
    queue := []struct {
        Node *TreeNode
        Sum  int
    }{
        {root, root.Val},
    }

    for len(queue) > 0 {
        current := queue[0]
        queue = queue[1:]
        node, sum := current.Node, current.Sum

        // 叶子节点且路径和等于目标值
        if node.Left == nil && node.Right == nil && sum == targetSum {
            return true
        }
        // 非叶子节点:将子节点与更新后的路径和入队
        if node.Left != nil {
            queue = append(queue, struct{Node *TreeNode; Sum int}{node.Left, sum + node.Left.Val})
        }
        if node.Right != nil {
            queue = append(queue, struct{Node *TreeNode; Sum int}{node.Right, sum + node.Right.Val})
        }
    }
    return false
}

关键点解析

  1. 队列初始化:根节点及其值作为初始状态入队。
  2. 路径和累积:每个子节点的路径和为父节点路径和加上当前节点值。
  3. 叶子节点检查:出队时检查是否为叶子节点且满足条件。

复杂度分析

  • 时间复杂度:O(n),每个节点访问一次。
  • 空间复杂度:O(n),队列存储最坏情况下的所有节点。

示例验证

func main() {
    // 示例1:输入 root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
    root1 := &TreeNode{Val:5,
        Left:  &TreeNode{Val:4, Left: &TreeNode{Val:11, Left: &TreeNode{Val:7}, Right: &TreeNode{Val:2}}},
        Right: &TreeNode{Val:8, Left: &TreeNode{Val:13}, Right: &TreeNode{Val:4, Right: &TreeNode{Val:1}}},
    }
    fmt.Println(hasPathSum(root1, 22)) // 输出: true

    // 示例2:输入 root = [1,2,3], targetSum = 5
    root2 := &TreeNode{Val:1, Left: &TreeNode{Val:2}, Right: &TreeNode{Val:3}}
    fmt.Println(hasPathSum(root2, 5)) // 输出: false
}

边界条件与注意事项

  1. 空树处理:根节点为空时无论 targetSum 是否为0,均返回 false(如示例3)。
  2. 负数节点:路径中间和可能为负数,因此必须遍历到叶子节点才能终止递归。
  3. 单个节点:若树仅有一个节点(根节点即叶子节点),直接判断其值是否等于 targetSum。

总结

    • 递归法:代码简洁,优先推荐使用,适合树较平衡的场景。
    • 迭代法:显式控制遍历过程,适合深度较大的树或避免栈溢出的场景。
      两种方法均能高效解决问题,选择时可根据具体场景权衡代码简洁性与内存消耗。
posted @ 2025-03-31 17:09  云隙之间  阅读(25)  评论(0)    收藏  举报