LeetCode 题解工作台
统计子树中城市之间最大距离
给你 n 个城市,编号为从 1 到 n 。同时给你一个大小为 n-1 的数组 edges ,其中 edges[i] = [u i , v i ] 表示城市 u i 和 v i 之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵 树 。 一棵 子树 是城市的一个子集…
5
题型
5
代码语言
3
相关题
当前训练重点
困难 · 二分·树·traversal
答案摘要
我们注意到 $n \leq 15$,因此可以考虑使用二进制枚举的方法枚举所有的子树。而子树中节点的最大距离,其实就是子树中两个节点之间的最长路径,也即是树的直径,求解树的直径一般可以使用 DFS 或 BFS,先找到树直径的一个端点,然后再从该端点出发,找到树的另一个端点,这两个端点之间的路径长度就是树的直径。 接下来,我们详细说明具体的代码实现。
Interview AiBoxInterview AiBox 实时 AI 助手,陪你讲清 二分·树·traversal 题型思路
题目描述
给你 n 个城市,编号为从 1 到 n 。同时给你一个大小为 n-1 的数组 edges ,其中 edges[i] = [ui, vi] 表示城市 ui 和 vi 之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵 树 。
一棵 子树 是城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。
对于 d 从 1 到 n-1 ,请你找到城市间 最大距离 恰好为 d 的所有子树数目。
请你返回一个大小为 n-1 的数组,其中第 d 个元素(下标从 1 开始)是城市间 最大距离 恰好等于 d 的子树数目。
请注意,两个城市间距离定义为它们之间需要经过的边的数目。
示例 1:

输入:n = 4, edges = [[1,2],[2,3],[2,4]]
输出:[3,4,0]
解释:
子树 {1,2}, {2,3} 和 {2,4} 最大距离都是 1 。
子树 {1,2,3}, {1,2,4}, {2,3,4} 和 {1,2,3,4} 最大距离都为 2 。
不存在城市间最大距离为 3 的子树。
示例 2:
输入:n = 2, edges = [[1,2]] 输出:[1]
示例 3:
输入:n = 3, edges = [[1,2],[2,3]] 输出:[2,1]
提示:
2 <= n <= 15edges.length == n-1edges[i].length == 21 <= ui, vi <= n- 题目保证
(ui, vi)所表示的边互不相同。
解题思路
方法一:二进制枚举 + BFS 或 DFS
我们注意到 ,因此可以考虑使用二进制枚举的方法枚举所有的子树。而子树中节点的最大距离,其实就是子树中两个节点之间的最长路径,也即是树的直径,求解树的直径一般可以使用 DFS 或 BFS,先找到树直径的一个端点,然后再从该端点出发,找到树的另一个端点,这两个端点之间的路径长度就是树的直径。
接下来,我们详细说明具体的代码实现。
我们先根据数组 构建出邻接表 ,其中 表示节点 的所有邻接节点。
用一个二进制数 表示子树,其中 的第 位为 表示节点 在子树中,否则表示节点 不在子树中。每个节点都有两种状态,即在子树中或不在子树中,有 个节点,因此一共有 种状态。
接下来,我们在 的范围内枚举子树 ,对于每个子树:
如果 的二进制表示中只有一个二进制位为 ,即 ,则跳过该 ,因为这些 表示的子树只有一个节点,不符合题意;
否则,我们找到 的二进制表示中最高位的二进制位为 的位置,记为 。然后从节点 出发,通过深度优先搜索或者广度优先搜索,找到树直径的一个端点 ,然后我们再从节点 出发,同样通过深度优先搜索或者广度优先搜索,过程中记录下最大距离 。
当走到最深的节点时,即可得知树的直径。此时我们更新答案数组 ,将 的值加 。注意,这里是 ,因为题目中的最大距离是从 开始计数的。
最后,枚举完所有的子树,返回答案数组 即可。
时间复杂度 ,空间复杂度 。其中 为节点个数。
class Solution:
def countSubgraphsForEachDiameter(
self, n: int, edges: List[List[int]]
) -> List[int]:
def dfs(u: int, d: int = 0):
nonlocal mx, nxt, msk
if mx < d:
mx, nxt = d, u
msk ^= 1 << u
for v in g[u]:
if msk >> v & 1:
dfs(v, d + 1)
g = defaultdict(list)
for u, v in edges:
u, v = u - 1, v - 1
g[u].append(v)
g[v].append(u)
ans = [0] * (n - 1)
nxt = mx = 0
for mask in range(1, 1 << n):
if mask & (mask - 1) == 0:
continue
msk, mx = mask, 0
cur = msk.bit_length() - 1
dfs(cur)
if msk == 0:
msk, mx = mask, 0
dfs(nxt)
ans[mx - 1] += 1
return ans
复杂度分析
| 指标 | 值 |
|---|---|
| 时间 | Depends on the final approach |
| 空间 | Depends on the final approach |
面试官常问的追问
外企场景- question_mark
Look for the candidate's understanding of bitmasking and dynamic programming techniques.
- question_mark
Assess the candidate's ability to handle tree traversal efficiently within constraints.
- question_mark
Evaluate their ability to optimize the algorithm, especially considering time and space complexity.
常见陷阱
外企场景- error
Not efficiently managing the bitmasking process and trying to iterate through all subsets without considering optimizations.
- error
Failing to validate whether a subset forms a connected graph before calculating the maximum distance between cities.
- error
Overlooking the importance of dynamic programming in storing precomputed values to avoid redundant calculations.
进阶变体
外企场景- arrow_right_alt
Increase the number of cities to test the scalability of the solution.
- arrow_right_alt
Consider additional constraints such as weighted edges or directed edges to modify the problem.
- arrow_right_alt
Modify the problem to count subtrees with a fixed number of cities and a maximum distance limit.