文章目录
- 1. 题目链接
- 2. 题目描述
- 3. 题目示例
- 4. 解题思路
- 5. 题解代码
- 6. 复杂度分析
1. 题目链接
1617. 统计子树中城市之间最大距离 - 力扣(LeetCode)
2. 题目描述
给你 n
个城市,编号为从 1
到 n
。同时给你一个大小为 n-1
的数组 edges
,其中 edges[i] = [ui, vi]
表示城市 ui
和 vi
之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵 树 。
一棵 子树 是城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。
对于 d
从 1
到 n-1
,请你找到城市间 最大距离 恰好为 d
的所有子树数目。
请你返回一个大小为 n-1
的数组,其中第 d
个元素(下标从 1 开始)是城市间 最大距离 恰好等于 d
的子树数目。
请注意,两个城市间距离定义为它们之间需要经过的边的数目。
3. 题目示例
示例 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]
4. 解题思路
- 问题理解:
- 给定一棵树,统计所有连通子图
- 对每个可能的直径d,计算有多少子图的直径等于d
- 直径定义为子图中最长路径的边数
- 核心算法:
- 使用回溯法枚举所有可能的节点子集(2^n种可能)
- 对每个子集检查是否构成连通子图
- 计算连通子图的直径并统计结果
- 直径计算:
- 采用树形DP方法
- 通过DFS计算每个节点的最长路径
- 在递归过程中维护全局直径
- 连通性检查:
- 比较访问标记数组和选择数组
- 两者一致说明子图连通
5. 题解代码
class Solution {private List<Integer>[] g; // 邻接表存储树结构private boolean[] inSet; // 记录哪些节点在当前子集中private boolean[] vis; // DFS访问标记private int[] ans; // 结果数组,ans[d]表示直径为d+1的子图数量private int n, diameter; // n是节点总数,diameter记录当前子图的直径public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {this.n = n;// 初始化邻接表g = new ArrayList[n];Arrays.setAll(g, e -> new ArrayList<>());// 构建树结构(节点编号转为0-based)for (var e : edges) {int x = e[0] - 1, y = e[1] - 1;g[x].add(y);g[y].add(x);}ans = new int[n - 1]; // 直径范围是1到n-1inSet = new boolean[n];f(0); // 开始递归枚举所有子集return ans;}// 递归枚举所有可能的节点子集private void f(int i) {// 递归终止条件:处理完所有节点if (i == n) {// 检查当前子集是否构成连通子图for (int v = 0; v < n; ++v)if (inSet[v]) {vis = new boolean[n];diameter = 0;dfs(v); // 从第一个选中的节点开始DFSbreak;}// 如果子图连通且直径有效,更新结果if (diameter > 0 && Arrays.equals(vis, inSet))++ans[diameter - 1];return;}// 不选当前节点if(i + 1);// 选当前节点iinSet[i] = true;f(i + 1);inSet[i] = false; // 回溯}// 计算子图的直径(同时标记访问过的节点)private int dfs(int x) {vis[x] = true;int maxLen = 0; // 记录从x出发的最长路径长度for (int y : g[x])if (!vis[y] && inSet[y]) { // 只考虑选中的且未访问的邻居int ml = dfs(y) + 1; // 递归计算子节点路径长度diameter = Math.max(diameter, maxLen + ml); // 更新直径maxLen = Math.max(maxLen, ml); // 更新当前最大长度}return maxLen;}
}
6. 复杂度分析
时间复杂度:O(n * 2^n)
- 枚举所有子集:O(2^n)
- 对每个子集进行DFS:O(n)
- 总复杂度为两者的乘积
空间复杂度:O(n)
- 邻接表存储空间:O(n)
- 递归调用栈深度:O(n)
- 各种标记数组:O(n)