ACM 选手图解 LeetCode 路径总和

简介: ACM 选手图解 LeetCode 路径总和

大家好呀,我是帅蛋。


今天解决路径总和,如果你看过我【二叉树的所有路径】这篇文章,你会发现这其实是在它解法的基础上做了一些改动。


咱们话不多说,直接开整。

640.png


   LeetCode 112:路径总和


题意


给定二叉树的根节点 root 和一个表示目标的整数 targetSum。


判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum。存在返回 true,否则返回 false。


示例


输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22

输出:true

解释:等于目标和的根节点到叶节点路径如下图所示。

640.png


提示


  • 树中节点的数目在范围 [0, 5000] 内
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000


题目解析


难度简单,如果你看过我【二叉树的所有路径】这篇文章,思路应该从你脑阔里立马跳出来。


如果你看过我足够多二叉树的文章,那你应该知道这个思路叫:自顶向下。


我在这里自顶向下用的其实就是前序遍历的方式,每次先判断当前的节点,再递归左子树,最后是右子树。


在本题路径总和中,自顶向下的解法其实就是:


  • 判断当前节点是否为叶子节点,如果是叶子节点,判断当前叶子节点的值是否 = targetSum 减去之前路径上节点值。
  • 递归左子树。
  • 递归右子树。


这种每次先根节点,再左子树,最后右子树,典型的前序遍历的方式。

640.png


递归法


递归法,那还是按照往常,祭出递归二步曲:


(1) 找出重复的子问题。


前序遍历的顺序是:根节点、左子树、右子树。


在本题同样也是这个顺序:判断节点,递归左子树,递归右子树


对于左子树和右子树来说,也都是同样的操作。


# 递归左子树
leftPath = self.hasPathSum(root.left, targetSum - root.val)
# 递归右子树
rightPath = self.hasPathSum(root.right, targetSum - root.val)

(2) 确定终止条件。


对于路径来说,遍历到叶子节点且叶子节点的值等于该路径之前节点的值,证明已找到,返回 True。

# 如果当前节点为叶子节点,且叶子节点的值等于减去该路径之前节点的值,返回 True
if root.left == None and root.right == None and root.val == targetSum:
    return True

最重要的两点确定完了,那总的代码也就出来了。


Python 代码实现


# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        # 如果树为空,返回 False
        if root == None:
            return False
        # 如果当前节点为叶子节点,且叶子节点的值等于减去该路径之前节点的值,返回 True
        if root.left == None and root.right == None and root.val == targetSum:
            return True
        # 递归左子树
        leftPath = self.hasPathSum(root.left, targetSum - root.val)
        # 递归右子树
        rightPath = self.hasPathSum(root.right, targetSum - root.val)
        return leftPath or rightPath


Java 代码实现

/**
 * 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;
        }
        // 如果当前节点为叶子节点,且叶子节点的值等于减去该路径之前节点的值,返回 True
        if(root.left == null && root.right == null && root.val == targetSum){
            return true;
        }
        // 递归左子树
        boolean leftPath = hasPathSum(root.left, targetSum - root.val);
        // 递归右子树
        boolean rightPath = hasPathSum(root.right, targetSum - root.val);
        return leftPath || rightPath;
    }
}


本题解,最坏情况下,每个节点都会被访问到,所以时间复杂度为 O(n)


此外在递归过程中调用了额外的栈空间,栈的大小取决于二叉树的高度,二叉树最坏情况下的高度为 n,所以空间复杂度为 O(n)


非递归法(迭代)


非递归的做法,很多人又叫迭代法迭代法就是不断地将旧的变量值,递推计算新的变量值


迭代法是用栈来做,对二叉树前序遍历迭代法不太了解的蛋粉可以看下面这篇文章:


ACM 选手带你玩转二叉树前中后序遍历(非递归版)


根据栈“先入后出”的特点,结合前序遍历的顺序,迭代的过程也就出来了:


每次都是先将节点放入栈,然后右子树,最后左子树。


具体步骤如下所示:


  • 初始化维护栈,将根节点及根节点的值入栈。
  • 当栈不为空时,弹出栈顶的节点及节点的值:
  • 若当前节点为叶子节点且累积值 val 为 targetSum,返回 true。
  • 若当前节点的右子树不为空,将右孩子及 “val + 右孩子的值”入栈。
  • 若当前节点的左子树不为空,将左孩子及”val + 左孩子的值“入栈。


以下图为例:

640.png

首先初始化栈 stack。

640.png



# 初始化栈
stack = [(root, root.val)]


当栈不为空,栈顶元素 node 和栈顶值 val 出栈。

640.png



node, val = stack.pop()


此时 node = 5,val = 5,此时 val 不等于 targetSum,此时 node 有左右子树,此时”右子树及路径值“先入栈,再”左子树及路径值“入栈。

640.png



# 右子树入栈
if node.right:
    stack.append((node.right, val + node.right.val))
# 左子树入栈
if node.left:
    stack.append((node.left, val + node.left.val))


接下来其实就是在重复上面的操作。


弹出栈顶元素 node = 4, val = 9,此时 val 不等于 targetSum,此时 node 有左子树,此时”左子树及路径值“入栈。

640.png


弹出栈顶元素 node =11, val = 20,此时 val 不等于 targetSum,此时 node 有左右子树,此时”右子树及路径值“先入栈,再”左子树及路径值“入栈。

640.png


弹出栈顶元素 node =7, val = 27,此时 node = 7 为叶子节点,但 val 不等于 targetSum。

640.png


继续弹出栈顶元素 node = 2,val = 22,此时 node 为叶子节点,且 val 等于 targetSum,证明该树中存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum。

640.png


# 如果当前节点为叶子节点且路径总和 = targetSum
if node.left == None and node.right == None and val == targetSum:
    return True


Python 代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        # 如果树为空,返回 False
        if root == None:
            return False
        # 初始化栈
        stack = [(root, root.val)]
        while stack:
            node, val = stack.pop()
            # 如果当前节点为叶子节点且路径总和 = targetSum
            if node.left == None and node.right == None and val == targetSum:
                return True
            # 右子树入栈
            if node.right:
                stack.append((node.right, val + node.right.val))
            # 左子树入栈
            if node.left:
                stack.append((node.left, val + node.left.val))
        return False



Java 代码实现

/**
 * 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;
        }
        // 初始化栈
        Stack<TreeNode> stack = new Stack<>();
        Stack<Integer> stackVal = new Stack<>();
        stack.push(root);
        stackVal.push(root.val);
        while(!stack.isEmpty()){
            int n = stack.size();
            for(int i = 0; i < n; i++){
                TreeNode node = stack.pop();
                int val = stackVal.pop();
                // 如果当前节点为叶子节点且路径总和 = targetSum
                if(node.left == null && node.right == null && val == targetSum){
                    return true;
                }
                // 右子树入栈
                if(node.right != null){
                    stack.push(node.right);
                    stackVal.push(val + node.right.val);
                }
                // 左子树入栈
                if(node.left != null){
                    stack.push(node.left);
                    stackVal.push(val + node.left.val);
                }
            }
        }
        return false;
    }
}

同样,本题解时间复杂度为 O(n)空间复杂度为 O(n)


图解路经总和到这就结束辣,你学废了嘛?


你看吧,其实数据结构与算法题啊,刷多了你就会发现似曾相识,切起题来就会一顺百顺。


这个道理我都交给你了,这不得马上安排个点赞 + 在看 + 转发大礼包嘛。


哈哈哈哈,我是帅蛋,我们下次见!


相关文章
|
1月前
【LeetCode 35】112.路径总和
【LeetCode 35】112.路径总和
23 0
|
4月前
|
算法 Unix 测试技术
力扣经典150题第五十二题:简化路径
力扣经典150题第五十二题:简化路径
42 0
|
1月前
【LeetCode 36】113.路径总和II
【LeetCode 36】113.路径总和II
28 0
|
3月前
|
机器人 Python
【Leetcode刷题Python】62. 不同路径
LeetCode 62题 "不同路径" 的Python解决方案,使用动态规划算法计算机器人从网格左上角到右下角的所有可能路径数量。
68 0
|
5月前
|
存储 SQL 算法
LeetCode题目113:多种算法实现 路径总和ll
LeetCode题目113:多种算法实现 路径总和ll
|
1月前
【LeetCode 34】257.二叉树的所有路径
【LeetCode 34】257.二叉树的所有路径
12 0
|
3月前
|
Python
【Leetcode刷题Python】113. 路径总和 II
LeetCode上113号问题"路径总和 II"的Python实现,通过深度优先搜索来找出所有从根节点到叶子节点路径总和等于给定目标和的路径。
41 3
【Leetcode刷题Python】113. 路径总和 II
|
3月前
|
存储 Python
【Leetcode刷题Python】64. 最小路径和
一种使用动态规划解决LeetCode上64题“最小路径和”的Python实现方法,通过维护一个一维数组来计算从网格左上角到右下角的最小路径总和。
32 1
【Leetcode刷题Python】64. 最小路径和
|
3月前
|
存储 算法 Linux
LeetCode第71题简化路径
文章讲述了LeetCode第71题"简化路径"的解题方法,利用栈的数据结构特性来处理路径中的"."和"..",实现路径的简化。
LeetCode第71题简化路径
|
3月前
|
算法
LeetCode第64题最小路径和
LeetCode第64题"最小路径和"的解题方法,运用动态规划思想,通过构建一个dp数组来记录到达每个点的最小路径和,从而高效求解。
LeetCode第64题最小路径和