Leetcode 几道 DFS 子树和路径问题杂记
563. 二叉树的坡度
给你一个二叉树的根节点 root ,计算并返回 整个树 的坡度 。
一个树的 节点的坡度 定义即为,该节点左子树的节点之和和右子树节点之和的 差的绝对值 。如果没有左子树的话,左子树的节点之和为 0 ;没有右子树的话也是一样。空结点的坡度是 0 。
整个树 的坡度就是其所有节点的坡度之和。
思路
采用后序遍历的方式,在计算出左右子树的和之后,计算当前节点的坡度。
代码
# 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 findTilt(self, root: Optional[TreeNode]) -> int:
ans = 0
def dfs(rt):
if not rt:
return 0
l_val = dfs(rt.left)
r_val = dfs(rt.right)
nonlocal ans
ans += abs(l_val - r_val)
return rt.val + l_val + r_val
dfs(root)
return ans
508. 出现次数最多的子树元素和
给你一个二叉树的根结点 root ,请返回出现次数最多的子树元素和。如果有多个元素出现的次数相同,返回所有出现次数最多的子树元素和(不限顺序)。
一个结点的 「子树元素和」 定义为以该结点为根的二叉树上所有结点的元素之和(包括结点本身)。
思路
参照 1339 的DFS遍历树的过程,对以当前节点为根的子树的和计数。最后根据计数输出出现次数最多的和的值。
代码
# 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 findFrequentTreeSum(self, root: Optional[TreeNode]) -> List[int]:
counter = defaultdict(int)
def dfs(rt):
nonlocal counter
left_value = dfs(rt.left) if rt.left else 0
right_value = dfs(rt.right) if rt.right else 0
s = rt.val + left_value + right_value
counter[s] += 1
return s
dfs(root)
mx_value = int(-1e9)
ans = []
for value, count in counter.items():
if count > mx_value:
mx_value = count
ans.clear()
ans.append(value)
elif count == mx_value:
ans.append(value)
return ans
1339. 分裂二叉树的最大乘积
给你一棵二叉树,它的根为 root 。请你删除 1 条边,使二叉树分裂成两棵子树,且它们子树和的乘积尽可能大。
思路
在树上删除 1 条边,会分裂成两个子树。整个树的节点和可以一次DFS求解出来。考虑每种子树划分的可能性,需要在DFS遍历树的过程中,保存以当前节点为根的子树的和。最后分别枚举每种删边的可能性,求解两部分乘积的最大值。
代码
# 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 maxProduct(self, root: Optional[TreeNode]) -> int:
s = 0
mod = int(1e9+7)
def calc_sum(rt):
nonlocal s
if not rt:
return 0
s = s + rt.val
calc_sum(rt.left)
calc_sum(rt.right)
calc_sum(root)
sums = []
def dfs(rt):
if not rt:
return 0
left_val = dfs(rt.left)
right_val = dfs(rt.right)
nonlocal sums
nonlocal mod
tree_sum = left_val + right_val + rt.val
sums.append(tree_sum)
return tree_sum
dfs(root)
sums = sums[:-1]
ans = -1
for part_a in sums:
part_b = s - part_a
ans = max(ans, part_a * part_b)
return ans % mod
124. 二叉树中的最大路径和
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
思路
对于树上的任一节点,树上的最大路径和要么以该节点为根且经过该节点、要么不经过该节点。对任一节点,只需要考虑以该节点为根且经过该节点的最大路径是多少,通过对数的搜索求最大值即可。那么问题转换成,如何求解以该节点为根且经过该节点 的最大路径。
以该节点为根且经过该节点 是 左子树的最大路径+右子树的最大路径+该节点的值,而左右子树的最大路径可以递归求解。需要注意的是,递归函数中的返回值是以该节点为根的最大路径:要么是左子树最大路径+该节点,要么是右子树最大路径+该节点。
代码
# 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 maxPathSum(self, root: Optional[TreeNode]) -> int:
ans = -inf
def dfs(rt):
if not rt:
return 0
left_value = dfs(rt.left)
right_value = dfs(rt.right)
nonlocal ans
ans = max(ans, rt.val + left_value + right_value)
return max(rt.val + max(left_value, right_value), 0)
dfs(root)
return ans
437. 路径总和 III
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
思路
思路1 自底向上求解
顺延刚才子树的子路,采用后序遍历的方式,记录以当前节点为根下的所有路径和可能性。例如,对于示例1中左下角3 3 -1的子树,就有 3/-2/(3,3)/(-2,3) 四种路径的可能性。在搜索函数中,只需要先分别对左子树和右子树统计路径可能性,再统计经过当前节点的所有路径可能,最后统计满足目标和的路径数量。
考虑时间复杂度,对于每个节点,需要更新其下为根的所有路径,因此时间复杂度是n^2。
思路2 自顶向下求解
考虑从根节点到其下某节点的一个路径,如果以这个节点结尾的部分路径满足targetSum的要求,那么会有 n_i + ... + n_j = targetSum ,其中n_i是这个节点的值,n_j是路径中的某个节点的值,进一步转换成前缀和的形式,为s_i - targetSum = s_j ,其中s_i 是根节点到节点 i 的和,s_j 是根节点到节点 j的和。这一位置,对于路径中的任一节点,边统计根节点到当前节点的和,边计算之前的路径的前缀中,有多少个 s_i - targetSum 就可以完成计数。
这样的只需要遍历一遍所有节点,时间复杂度为 O(n).
代码
# 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 pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
# 思路2 前缀和
ans = 0
path_prefix = defaultdict(int)
path_prefix[0] = 1
def dfs(rt, s):
nonlocal ans
nonlocal path_prefix
if not rt:
return
s += rt.val
ans += path_prefix[s - targetSum]
path_prefix[s] += 1
dfs(rt.left, s)
dfs(rt.right, s)
path_prefix[s] -= 1
dfs(root, 0)
return ans
# 思路 1 子树
# ans = 0
# def dfs(rt):
# if not rt:
# return {}
# left_path_cnt = dfs(rt.left)
# right_path_cnt = dfs(rt.right)
# node_path = defaultdict(int)
# node_path[rt.val] = 1
# for value, cnt in left_path_cnt.items():
# node_path[value + rt.val] += cnt
# for value, cnt in right_path_cnt.items():
# node_path[value + rt.val] += cnt
# nonlocal ans
# ans += node_path[targetSum]
# return node_path
# dfs(root)
# return ans

浙公网安备 33010602011771号