携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情
大家好呀,我是帅蛋。
今天解决路径总和,如果你看过我【二叉树的所有路径】这篇文章,你会发现这其实是在它解法的基础上做了一些改动。
咱们话不多说,直接开整。
LeetCode 112:路径总和
题意
给定二叉树的根节点 root 和一个表示目标的整数 targetSum。
判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum。存在返回 true,否则返回 false。
示例
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如下图所示。
提示
- 树中节点的数目在范围 [0, 5000] 内
- -1000 <= Node.val <= 1000
- -1000 <= targetSum <= 1000
题目解析
难度简单,如果你看过我【二叉树的所有路径】这篇文章,思路应该从你脑阔里立马跳出来。
如果你看过我足够多二叉树的文章,那你应该知道这个思路叫:自顶向下。
我在这里自顶向下用的其实就是前序遍历的方式,每次先判断当前的节点,再递归左子树,最后是右子树。
在本题路径总和中,自顶向下的解法其实就是:
- 判断当前节点是否为叶子节点,如果是叶子节点,判断当前叶子节点的值是否 = targetSum 减去之前路径上节点值。
- 递归左子树。
- 递归右子树。
这种每次先根节点,再左子树,最后右子树,典型的前序遍历的方式。
递归法
用递归法,那还是按照往常,祭出递归二步曲:
(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) 。
非递归法(迭代)
非递归的做法,很多人又叫迭代法。迭代法就是不断地将旧的变量值,递推计算新的变量值。
迭代法是用栈来做,对二叉树前序遍历迭代法不太了解的蛋粉可以看下面这篇文章:
根据栈“先入后出”的特点,结合前序遍历的顺序,迭代的过程也就出来了:
每次都是先将节点放入栈,然后右子树,最后左子树。
具体步骤如下所示:
- 初始化维护栈,将根节点及根节点的值入栈。
- 当栈不为空时,弹出栈顶的节点及节点的值:
-
- 若当前节点为叶子节点且累积值 val 为 targetSum,返回 true。
- 若当前节点的右子树不为空,将右孩子及 “val + 右孩子的值”入栈。
- 若当前节点的左子树不为空,将左孩子及”val + 左孩子的值“入栈。
以下图为例:
首先初始化栈 stack。
# 初始化栈
stack = [(root, root.val)]
当栈不为空,栈顶元素 node 和栈顶值 val 出栈。
node, val = stack.pop()
此时 node = 5,val = 5,此时 val 不等于 targetSum,此时 node 有左右子树,此时”右子树及路径值“先入栈,再”左子树及路径值“入栈。
# 右子树入栈
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 有左子树,此时”左子树及路径值“入栈。
弹出栈顶元素 node =11, val = 20,此时 val 不等于 targetSum,此时 node 有左右子树,此时”右子树及路径值“先入栈,再”左子树及路径值“入栈。
弹出栈顶元素 node =7, val = 27,此时 node = 7 为叶子节点,但 val 不等于 targetSum。
继续弹出栈顶元素 node = 2,val = 22,此时 node 为叶子节点,且 val 等于 targetSum,证明该树中存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum。
# 如果当前节点为叶子节点且路径总和 = 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) 。
图解路经总和到这就结束辣,你学废了嘛?
你看吧,其实数据结构与算法题啊,刷多了你就会发现似曾相识,切起题来就会一顺百顺。
这个道理我都交给你了,这不得马上安排个点赞嘛。
哈哈哈哈,我是帅蛋,我们下次见!