[英雄星球六月集训LeetCode解题日报] 第15日 深度优先搜索
一、508. 出现次数最多的子树元素和
1. 题目描述
给你一个二叉树的根结点 root ,请返回出现次数最多的子树元素和。如果有多个元素出现的次数相同,返回所有出现次数最多的子树元素和(不限顺序)。
一个结点的 「子树元素和」 定义为以该结点为根的二叉树上所有结点的元素之和(包括结点本身)。
2. 思路分析
DFS模拟即可,用一个计数器记录每个和出现次数,最后输出最多的那些和。
3. 代码实现
class Solution:
def findFrequentTreeSum(self, root: TreeNode) -> List[int]:
cnt = collections.defaultdict(int)
def dfs(root):
if not root:
return 0
ans = root.val
if root.left:
ans += dfs(root.left)
if root.right:
ans += dfs(root.right)
cnt[ans] += 1
return ans
dfs(root)
m = max(cnt.values())
ans = [k for k,v in cnt.items() if v == m]
return ans
二、655. 输出二叉树
链接: 655. 输出二叉树
1. 题目描述
在一个 m*n 的二维字符串数组中输出二叉树,并遵守以下规则:
行数 m 应当等于给定二叉树的高度。
列数 n 应当总是奇数。
根节点的值(以字符串格式给出)应当放在可放置的第一行正中间。根节点所在的行与列会将剩余空间划分为两部分(左下部分和右下部分)。你应该将左子树输出在左下部分,右子树输出在右下部分。左下和右下部分应当有相同的大小。即使一个子树为空而另一个非空,你不需要为空的子树输出任何东西,但仍需要为另一个子树留出足够的空间。然而,如果两个子树都为空则不需要为它们留出任何空间。
每个未使用的空间应包含一个空的字符串""。
使用相同的规则输出子树。
2. 思路分析
由于空树也要输出"",因此实际是输出一个完全二叉树的位置。
- 先计算深度,确定输出的矩阵有几行。
- 宽度=2^深度-1
- 对每层来说,节点应该在当前层
管辖区域
的中间位置。 管辖区域
的边界实际上父节点知道,就是父节点两侧。- 因此dfs传递层深和左右边界位置即可。
- 根节点(第一层)管家一整行。
3. 代码实现
class Solution:
def printTree(self, root: TreeNode) -> List[List[str]]:
def find_depth(root):
if not root:
return 0
return max(find_depth(root.left),find_depth(root.right))+1
m = find_depth(root)
n = 2**m -1
ans = [['']*n for _ in range(m)]
def dfs(root,depth,l,r):
if not root:
return
mid = (l+r)//2
ans[depth][mid] = str(root.val)
dfs(root.left, depth+1,l,mid-1)
dfs(root.right, depth+1,mid+1,r)
dfs(root,0,0,n-1)
return ans
三、 1026. 节点与其祖先之间的最大差值
1. 题目描述
给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。
(如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)
2. 思路分析
考虑一个数字和一个集合中任意数字的最大差值,一定是取集合中的最大值或最小值做差。
- 那么dfs时传递当前长辈节点中的最大最小值即可。
- 注意父节点没有,因此dfs入口可以从父节点的左右儿子进。
3. 代码实现
class Solution:
def maxAncestorDiff(self, root: Optional[TreeNode]) -> int:
ans = 0
def dfs(root,_min,_max):
if not root:
return
nonlocal ans
ans = max([ans,abs(_min - root.val),abs(_max-root.val)])
dfs(root.left,min(_min,root.val),max(_max,root.val))
dfs(root.right,min(_min,root.val),max(_max,root.val))
dfs(root.left,root.val,root.val)
dfs(root.right,root.val,root.val)
return ans
四、 1600. 王位继承顺序
链接: 1600. 王位继承顺序
1. 题目描述
一个王国里住着国王、他的孩子们、他的孙子们等等。每一个时间点,这个家庭里有人出生也有人死亡。
这个王国有一个明确规定的王位继承顺序,第一继承人总是国王自己。我们定义递归函数 Successor(x, curOrder) ,给定一个人 x 和当前的继承顺序,该函数返回 x 的下一继承人。
Successor(x, curOrder):
如果 x 没有孩子或者所有 x 的孩子都在 curOrder 中:
如果 x 是国王,那么返回 null
否则,返回 Successor(x 的父亲, curOrder)
否则,返回 x 不在 curOrder 中最年长的孩子
比方说,假设王国由国王,他的孩子 Alice 和 Bob (Alice 比 Bob 年长)和 Alice 的孩子 Jack 组成。
- 一开始, curOrder 为 [“king”].
- 调用 Successor(king, curOrder) ,返回 Alice ,所以我们将 Alice 放入 curOrder 中,得到 [“king”, “Alice”] 。
- 调用 Successor(Alice, curOrder) ,返回 Jack ,所以我们将 Jack 放入 curOrder 中,得到 [“king”, “Alice”, “Jack”] 。
- 调用 Successor(Jack, curOrder) ,返回 Bob ,所以我们将 Bob 放入 curOrder 中,得到 [“king”, “Alice”, “Jack”, “Bob”] 。
- 调用 Successor(Bob, curOrder) ,返回 null 。最终得到继承顺序为 [“king”, “Alice”, “Jack”, “Bob”] 。
通过以上的函数,我们总是能得到一个唯一的继承顺序。
请你实现 ThroneInheritance 类:
- ThroneInheritance(string kingName) 初始化一个 ThroneInheritance 类的对象。国王的名字作为构造函数的参数传入。
- void birth(string parentName, string childName) 表示 parentName 新拥有了一个名为 childName 的孩子。
- void death(string name) 表示名为 name 的人死亡。一个人的死亡不会影响 Successor 函数,也不会影响当前的继承顺序。你可以只将这个人标记为死亡状态。
- string[] getInheritanceOrder() 返回 除去 死亡人员的当前继承顺序列表。
2. 思路分析
读题读了半个小时。
最后发现就是一个树的前序遍历(先根遍历)。
用一个字典记录每个人的节点位置,便于生孩子。
输出时跳过死人即可。
3. 代码实现
class Node:
def __init__(self, name: str):
self.name = name
self.children = []
class ThroneInheritance:
def __init__(self, kingName: str):
self.king = Node(kingName)
self.person = {kingName:self.king}
self.deaths = set()
def birth(self, parentName: str, childName: str) -> None:
child = Node(childName)
self.person[childName] = child
self.person[parentName].children.append(child)
def death(self, name: str) -> None:
self.deaths.add(self.person[name])
def getInheritanceOrder(self) -> List[str]:
deaths = self.deaths
ans = []
def dfs(king):
if not king:
return
if king not in deaths:
ans.append(king.name)
for child in king.children:
dfs(child)
dfs(self.king)
return ans
五、 124. 二叉树中的最大路径和
链接: 124. 二叉树中的最大路径和
1. 题目描述
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
2. 思路分析
再来一题。
也算是树上dp,自己的状态从子树转移而来。
定义dp[cur]为以自己为祖宗节点的一条单边路径的最大权。
对于任意节点,自己加上左右两条单边路径(可不取),就能组成所有路径。
那么:
ans = max{dp[l]+dp[r]+val|dp[l]>0,dp[r]>0} 其中l,r是val这个节点的左右孩子
3. 代码实现
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
"""
dfs返回以本节点为祖宗节点的上升路径最大值。
根据左右节点的dfs结果更新一下答案:
左右节点+本节点一定可以组成一条路径,自己必须参加,左右节点选正数从参加。计算最大路径,更新答案
自己返回的结果应该是左右两边大的+自己,如果都是负的就都不要,只要自己。
"""
ans = -10**9
def dfs(root):
if not root:
return 0
m = root.val
left = dfs(root.left)
right = dfs(root.right)
if left > 0:
m += left
if right > 0:
m += right
nonlocal ans
ans = max(ans,m)
ret = root.val
m = max(left,right)
if m > 0:
ret += m
return ret
dfs(root)
return ans