二叉树-02
1. 路径总和 II
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
真题点击此处:113. 路径总和 II
1.1 深度优先搜索
解题方法:
- 定义一个ans列表用于存储所有符合条件的路径,定义一个path列表用于存储当前遍历的路径。
- 创建一个深度优先搜索函数dfs,该函数接收两个参数:当前节点root和目标和targetSum。
- 在dfs函数中,首先判断当前节点是否为空,如果为空则直接返回。
- 在path列表中添加当前节点的值,并将目标和减去当前节点的值。
- 如果当前节点为叶子节点并且目标和已经减少至0,则将当前path列表拷贝到ans列表中,表示找到了一条符合条件的路径。
- 递归调用dfs函数遍历当前节点的左子树和右子树。
- 在递归调用结束后,将path列表中的最后一个元素移除,以回退到上一层节点继续遍历。
- 在主函数中调用dfs函数进行深度优先搜索,并返回最终的结果。
以下为代码实现:
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
ans=[]
path=[]
def dfs(root, targetSum):
if root is None:
return
path.append(root.val)
targetSum -= root.val
if not root.left and not root.right and targetSum == 0:
ans.append(path[:])
dfs(root.left, targetSum)
dfs(root.right, targetSum)
path.pop()
dfs(root, targetSum)
return ans
时间复杂度:O(N2),其中 N 是树的节点数。在最坏情况下,树的上半部分为链状,下半部分为完全二叉树,并且从根节点到每一个叶子节点的路径都符合题目要求。此时,路径的数目为 O(N),并且每一条路径的节点个数也为 O(N),因此要将这些路径全部添加进答案中,时间复杂度为 O(N2)。
空间复杂度:O(N),其中 N 是树的节点数。空间复杂度主要取决于栈空间的开销,栈中的元素个数不会超过树的节点数。
1.2 广度优先搜索
解题方法:
- 首先定义了一个成员变量ans,用于存储所有符合条件的路径,同时定义了一个map用于记录节点以及其对应的父节点。
- 创建了一个pathSum方法,接收二叉树的根节点和目标和作为参数。
- 首先对根节点进行空值检查,如果根节点为空则直接返回ans。
- 接着创建两个队列queueNode和queueSum,分别用于存储当前节点和从根节点到当前节点的路径和。
- 将根节点和路径和0分别加入队列中。
- 使用while循环遍历队列,直到队列为空为止。
- 在循环中,首先弹出队列中的节点和路径和,并计算出当前节点的累计和rec。
- 如果当前节点是叶子节点且累计和等于目标和,则调用getPath方法获取路径。
- 将当前节点的左右子节点加入队列,并更新它们的累计和。
- getPath方法用于根据map中记录的父节点信息,从叶子节点向上回溯得到完整的路径,并将路径添加到ans中。
以下为代码实现:
class Solution {
List<List<Integer>> ans = new LinkedList<List<Integer>>();
Map<TreeNode, TreeNode> map = new HashMap<TreeNode, TreeNode>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root == null){
return ans;
}
Queue<TreeNode> queueNode = new LinkedList<>();
Queue<Integer> queueSum = new LinkedList<>();
queueNode.offer(root);
queueSum.offer(0);
while(!queueNode.isEmpty()){
TreeNode node = queueNode.poll();
int rec = queueSum.poll() + node.val;
if(node.left == null && node.right == null){
if(targetSum == rec){
getPath(node);
}
}
if(node.left != null){
map.put(node.left, node);
queueNode.offer(node.left);
queueSum.offer(rec);
}
if(node.right != null){
map.put(node.right, node);
queueNode.offer(node.right);
queueSum.offer(rec);
}
}
return ans;
}
public void getPath(TreeNode node){
List<Integer> temp = new LinkedList<Integer>();
while(node != null){
temp.add(node.val);
node = map.get(node);
}
Collections.reverse(temp);
ans.add(new LinkedList<Integer>(temp));
}
}
时间复杂度:O(N2),其中 N 是树的节点数。分析思路与方法一相同。
空间复杂度:O(N),其中 NNN 是树的节点数。空间复杂度主要取决于哈希表和队列空间的开销,哈希表需要存储除根节点外的每个节点的父节点,队列中的元素个数不会超过树的节点数。
2. 求根节点到叶节点数字之和
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
真题点击此处:129. 求根节点到叶节点数字之和
2.1 深度优先搜索
解题方法:
- 定义了一个sumNumbers方法,接收二叉树的根节点作为参数。
- 在sumNumbers方法中定义了一个内部的dfs函数,用于实现深度优先搜索。
- dfs函数接收两个参数:当前节点和从根节点到当前节点的路径数字之和。
- 如果当前节点为空,则直接返回0。
- 计算当前节点的路径数字之和,即prevTotal * 10 + root.val。
- 如果当前节点是叶子节点,则返回路径数字之和。
- 如果当前节点不是叶子节点,则递归地对左右子节点调用dfs函数,并将当前节点的路径数字之和传入。
- 将左右子节点的计算结果相加并返回给上一层递归调用。
以下为代码实现:
class Solution:
def sumNumbers(self, root: TreeNode) -> int:
def dfs(root: TreeNode, prevTotal: int) -> int:
if not root:
return 0
total = prevTotal * 10 + root.val
if not root.left and not root.right:
return total
else:
return dfs(root.left, total) + dfs(root.right, total)
return dfs(root, 0)
时间复杂度:O(n),其中 n 是二叉树的节点个数。对每个节点访问一次。
空间复杂度:O(n),其中 n 是二叉树的节点个数。空间复杂度主要取决于递归调用的栈空间,递归栈的深度等于二叉树的高度,最坏情况下,二叉树的高度等于节点个数,空间复杂度为O(n)。
2.2 广度优先搜索
解题方法:
- 首先对根节点进行空值检查,如果根节点为空则直接返回0。
- 定义一个整型变量sum用于存储最终求和结果。
- 创建两个队列nodeQueue和numQueue,分别用于存储当前节点和从根节点到当前节点的路径数字之和。
- 将根节点和其值分别加入两个队列中。
- 使用while循环遍历节点队列,直到队列为空为止。
- 在循环中,首先弹出队列中的节点和路径数字之和,并将其分别存储在node和num变量中。
- 如果当前节点是叶子节点,则将num累加到sum中。
- 如果当前节点不是叶子节点,则将其左右子节点加入队列中,并更新它们的路径数字之和。
- 在方法结束时,返回求和结果sum。
以下为代码实现:
class Solution {
public int sumNumbers(TreeNode root) {
if (root == null) {
return 0;
}
int sum = 0;
Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
Queue<Integer> numQueue = new LinkedList<Integer>();
nodeQueue.offer(root);
numQueue.offer(root.val);
while (!nodeQueue.isEmpty()) {
TreeNode node = nodeQueue.poll();
int num = numQueue.poll();
TreeNode left = node.left, right = node.right;
if (left == null && right == null) {
sum += num;
} else {
if (left != null) {
nodeQueue.offer(left);
numQueue.offer(num * 10 + left.val);
}
if (right != null) {
nodeQueue.offer(right);
numQueue.offer(num * 10 + right.val);
}
}
}
return sum;
}
}
时间复杂度:O(n),其中 n 是二叉树的节点个数。对每个节点访问一次。
空间复杂度:O(n,其中 n 是二叉树的节点个数。空间复杂度主要取决于队列,每个队列中的元素个数不会超过 n。
3. 统计二叉树中好节点的数目
给你一棵根为 root 的二叉树,请你返回二叉树中好节点的数目。
「好节点」X 定义为:从根到该节点 X 所经过的节点中,没有任何节点的值大于 X 的值。
真题点击此处:1448. 统计二叉树中好节点的数目
3.1 深度优先搜索
解题思路:
- 定义了一个goodNodes方法,接收二叉树的根节点作为参数。
- 在goodNodes方法中定义了一个内部的dfs函数,用于实现深度优先搜索。
- dfs函数接收两个参数:当前节点和当前路径上的最大值mx。
- 如果当前节点为空,则直接返回。
- 使用nonlocal关键字声明ans变量为非局部变量,用于记录"好节点"的数量。
- 如果当前节点的值大于等于mx,则将"好节点"数量加1,并更新最大值mx为当前节点的值。
- 递归地对左右子节点调用dfs函数,并传入更新后的最大值mx。
- 在方法结束时,返回"好节点"的数量ans。
以下为代码实现:
class Solution:
def goodNodes(self, root: TreeNode) -> int:
ans = 0
def dfs(root, mx):
if root is None:
return
nonlocal ans
if root.val >= mx:
ans += 1
mx = root.val
dfs(root.left, mx)
dfs(root.right, mx)
dfs(root, float('-inf'))
return ans
时间复杂度:O(n),其中 n 为二叉树中的节点个数。在深度优先遍历的过程中,每个节点只会被遍历一次。
空间复杂度:O(n)。由于我们使用递归来实现深度优先遍历,因此空间复杂度的消耗主要在栈空间,取决于二叉树的高度,最坏情况下二叉树的高度为 O(n)。
3.2 广度优先搜索
解题思路:
- 定义了一个goodNodes方法,接收二叉树的根节点作为参数。
- 首先初始化一个整型变量ans用于记录"好节点"的数量,并对根节点进行空值检查。
- 创建两个队列queueNode和queueVal,分别用于存储当前节点和当前路径上的最大值。
- 将根节点和Integer.MIN_VALUE(负无穷)加入两个队列中。
- 使用while循环遍历节点队列,直到队列为空为止。
- 在循环中,首先从两个队列中分别弹出当前节点node和当前路径上的最大值val。
- 如果当前节点的值大于等于当前路径上的最大值,则将"好节点"数量ans加1,并更新最大值val为当前节点的值。
- 如果当前节点有左子节点,则将其加入队列,并将当前路径上的最大值val加入到值队列中。
- 如果当前节点有右子节点,则将其加入队列,并将当前路径上的最大值val加入到值队列中。
- 最终返回"好节点"的数量ans。
以下为代码实现:
class Solution {
public int goodNodes(TreeNode root) {
int ans = 0;
if(root == null){
return 0;
}
Queue<TreeNode> queueNode = new LinkedList<>();
Queue<Integer> queueVal = new LinkedList<>();
queueNode.offer(root);
queueVal.offer(Integer.MIN_VALUE);
while(!queueNode.isEmpty()){
TreeNode node = queueNode.poll();
int val = queueVal.poll();
if(node.val >= val){
ans++;
val = node.val;
}
if(node.left != null){
queueNode.offer(node.left);
queueVal.offer(val);
}
if(node.right != null){
queueNode.offer(node.right);
queueVal.offer(val);
}
}
return ans;
}
}
时间复杂度:O(N),采用广度优先搜索的方式遍历整棵树,其中N表示二叉树中节点的数量。
空间复杂度:O(N),使用了两个队列来辅助遍历节点,因此额外空间复杂度取决于队列中元素的数量。