LeetCode每日一题2025-05-28
3372. 连接两棵树后最大目标节点数目 I M
有两棵 无向 树,分别有 n 和 m 个树节点。两棵树中的节点编号分别为[0, n - 1] 和 [0, m - 1] 中的整数。
给你两个二维整数 edges1 和 edges2 ,长度分别为 n - 1 和 m - 1 ,其中 edges1[i] = [aᵢ, bᵢ] 表示第一棵树中节点 ai 和 bi 之间有一条边,edges2[i] = [uᵢ, vᵢ] 表示第二棵树中节点 uᵢ 和 vᵢ 之间有一条边。同时给你一个整数 k 。
如果节点 u 和节点 v 之间路径的边数小于等于 k ,那么我们称节点 u 是节点 v 的 目标节点 。注意 ,一个节点一定是它自己的 目标节点 。
请你返回一个长度为 n 的整数数组 answer ,answer[i] 表示将第一棵树中的一个节点与第二棵树中的一个节点连接一条边后,第一棵树中节点 i 的 目标节点 数目的 最大值 。
注意 ,每个查询相互独立。意味着进行下一次查询之前,你需要先把刚添加的边给删掉。
示例 1:
输入: edges1 = [[0,1],[0,2],[2,3],[2,4]], edges2 = [[0,1],[0,2],[0,3],[2,7],[1,4],[4,5],[4,6]], k = 2
输出: [9,7,9,8,8]
解释:
- 对于
i = 0,连接第一棵树中的节点 0 和第二棵树中的节点 0 。- 对于
i = 1,连接第一棵树中的节点 1 和第二棵树中的节点 0 。- 对于
i = 2,连接第一棵树中的节点 2 和第二棵树中的节点 4 。- 对于
i = 3,连接第一棵树中的节点 3 和第二棵树中的节点 4 。- 对于
i = 4,连接第一棵树中的节点 4 和第二棵树中的节点 4 。

示例 2:
输入: edges1 = [[0,1],[0,2],[0,3],[0,4]], edges2 = [[0,1],[1,2],[2,3]], k = 1
输出: [6,3,3,3,3]
解释:
对于每个
i,连接第一棵树中的节点i和第二棵树中的任意一个节点。

提示:
2 <= n, m <= 1000edges1.length == n - 1edges2.length == m - 1edges1[i].length == edges2[i].length == 2edges1[i] = [aᵢ, bᵢ]0 <= aᵢ, bᵢ < nedges2[i] = [uᵢ, vᵢ]0 <= uᵢ, vᵢ < m- 输入保证
edges1和edges2都表示合法的树。 0 <= k <= 1000
问题分析
- 给定两棵无向树:
- 第一棵树有 个节点,编号 ,由
edges1长度为 的边列表表示; - 第二棵树有 个节点,编号 ,由
edges2长度为 的边列表表示。
- 第一棵树有 个节点,编号 ,由
- 定义:对于两棵树中任何两个节点 和 ,如果它们之间的路径边数 ,则称 是 的“目标节点”。
- 额外操作:对第一棵树中的某个节点 ,与第二棵树中的某个节点 连一条新边(长度记为 1)。然后整个图就是两棵树通过这条新边连接成一个整体。
- 需要求:对于第一棵树中每个 ,尝试将 与第二棵树中某个 连边后,统计“目标节点”的总个数,取最大值。输出长度为 的数组
answer,其中answer[i]为节点 能获得的最大“目标节点”数量。 - 注意:每次查询相互独立,添加完这条边后要把它删掉再进行下一次查询。
-
“目标节点”计数可以分为两部分:
- 第一棵树内的“目标节点”:只考虑第一棵树原有的结构,不受连接第二棵树的影响。
- 跨树到第二棵树的“目标节点”:路径必须经过新边,即先从 到 (零步),走新边到 (1 步),再在第二棵树中走若干步到 ;总步数 等价于 第二棵树中 。
-
因此,对每个 :
- 第一部分:统计在第一棵树里,以 为起点,所有 的节点个数,记为 。
- 第二部分:选一个在第二棵树的连接点 ,使得以 为起点,在第二棵树内 的节点数最多。记第二棵树上每个节点到半径 内的可达节点数量为 ,只需取 。
- 故 。
算法思路
- 推断节点个数
- 第一棵树节点数 ;
- 第二棵树节点数 。
- 构造邻接表
- 用
g1表示第一棵树的邻接表,大小为 ; - 用
g2表示第二棵树的邻接表,大小为 。
- 用
- BFS 统计“半径内可达”节点数
定义函数bfs_count(adj, radius):- 输入:邻接表
adj(节点数为 ),半径radius; - 对每个节点 从 出发做一次 BFS,维护一个长度为 的距离数组
dist,初始dist[i]=0其余-1; - 当从队列中弹出节点 时,如果
dist[u] > radius就跳过不扩展;否则把 计入计数器,再将所有未访问邻居 (dist[w]==-1)标记dist[w]=dist[u]+1并入队; - 最终统计以 为起点满足距离
radius的节点总数,存入数组counts[i]; - 返回长度为 的列表
counts。
- 输入:邻接表
- 计算两棵树的
- 对第一棵树调用
bfs_count(g1, k),得到 ; - 对第二棵树调用
bfs_count(g2, k-1),得到 。- 特殊情况:当 时,
radius = k-1 = -1。在实现里,只有dist[j]=0时才满足dist[j] \le -1不成立,因此只能把 $j` 自己计入一次,符合“节点一定算自己一个目标节点”的要求。
- 特殊情况:当 时,
- 对第一棵树调用
- 组合答案
- 先求
max_C2 = max(C2); - 然后对每个 ,
answer[i] = C1[i] + max_C2。
- 先求
时间复杂度
- 记第一棵树节点数为 ,第二棵树节点数为 。
bfs_count(g1, k)需要对 个节点分别做一次 BFS,每次遍历整棵树 ,共 。bfs_count(g2, k-1)共 。- 计算最大值及拼答案线性扫描均为 。
- 因此总时间复杂度为 。
- 在题目约束 下,,可以在常规硬件环境下接受。
代码分解
-
类与方法签名
1
2class Solution:
def maxTargetNodes(self, edges1: List[List[int]], edges2: List[List[int]], k: int) -> List[int]:- 直接接收
edges1, edges2, k,内部推断 。
- 直接接收
-
推断节点数与构造邻接表
1
2
3
4
5
6
7
8
9
10
11
12n = len(edges1) + 1
m = len(edges2) + 1
g1 = [[] for _ in range(n)]
for u,v in edges1:
g1[u].append(v)
g1[v].append(u)
g2 = [[] for _ in range(m)]
for u,v in edges2:
g2[u].append(v)
g2[v].append(u) -
BFS 统计函数 bfs_count
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21def bfs_count(adj: List[List[int]], radius: int) -> List[int]:
sz = len(adj)
counts = [0] * sz
from collections import deque
for i in range(sz):
dist = [-1] * sz
dist[i] = 0
dq = deque([i])
cnt = 0
while dq:
u = dq.popleft()
if dist[u] > radius:
continue
cnt += 1
for w in adj[u]:
if dist[w] == -1:
dist[w] = dist[u] + 1
dq.append(w)
counts[i] = cnt
return counts- 对每个
i初始化距离数组dist,执行典型的 BFS。 - 如果
dist[u] > radius就不再向下扩散。 - 统计所有满足
dist[u] <= radius的节点总数。
- 对每个
-
计算 并组合
1
2
3
4
5
6C1 = bfs_count(g1, k)
C2 = bfs_count(g2, k-1)
max_C2 = max(C2) if C2 else 0
answer = [C1_i + max_C2 for C1_i in C1]
return answer- 第一棵树用半径 ,第二棵树用半径 。
- 取第二个列表的最大值加到 上。
代码实现
1 | from collections import deque |