此题的思路不难想,使用枚举+树形dp
通过枚举把所有的子集列举出来,通过inSet布尔数组来表示。
树形dp是因为 子树的直径 就等于 城市间的最大距离。
因为道路是双向的,所以两个节点有相邻关系就可以互为父子。为了避免循环,采用vis[]数组来记录当前节点是否已经递归过了。并且vis[]数组还可以通过与inSet[]比较来判断是否所有的节点都已经遍历过了,防止把森林的情况也加进去了。
class Solution {
List<Integer>[] g;
//inSet 判断当前节点是否被选中成为子集
//vis 判断当前节点是否在之前遍历时候被访问过
private boolean[] inSet, vis;
int[] res;
//最大直径
int tmp;
int Length;
int diameter;
public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {
Length = n;
//给g初始化
g = new ArrayList[n];
inSet = new boolean[n];
res = new int[n-1];
Arrays.setAll(g,e -> new ArrayList<Integer>());
for(int[] node: edges){
int fa = node[0] - 1;
int son = node[1] - 1;
g[fa].add(son);
g[son].add(fa);
}
enumeration(0);
return res;
}
//枚举函数 i表示当前遍历到的城市序号
private void enumeration(int i){
if (i == Length){
//子集选取完毕,开始遍历子集中所有节点,找出最大直径。
for (int j = 0; j < Length; j++) {
//只要找出第一个在子集中的元素就能求出整个子集的最大直径
if (inSet[j]){
diameter = 0;
vis = new boolean[Length];
vis[j] =true;
tmp = 0;
dfs(j);
break;
}
}
//这个判断条件牛逼,把森林给略去了
if (diameter > 0 && Arrays.equals(vis, inSet)){
++res[diameter - 1];
}
return;
}
//不选
// enumeration(i+1);
try {
enumeration(i+1);
} catch (Exception e){
System.out.println(i);
}
//选
inSet[i] = true;
enumeration(i+1);
inSet[i] = false;
}
//树形dp,计算直径。注意返回的是最长链长,不是直径,直径通过ans存储。
//注意,之前忽视了一点,求最长直径,最长直径不一定经过根节点,因为每次递归都记录了最大值。
private int dfs(int x){
int maxLen = 0;
for (Integer son : g[x]) {
//判断当前节点是否被遍历过,防止成环
if(inSet[son] && !vis[son]){
vis[son] =true;
int len = dfs(son) + 1;
diameter = Math.max(diameter, maxLen + len);
maxLen = Math.max(maxLen, len);
}
}
return maxLen;
}
}