310.最小高度数,换根DP解法
题目
代码
class Solution {
// 用于存储每个节点到达其最深叶子节点的最大深度
public int[] deep;
// 在重新选择根节点过程中用于暂存深度的动态规划数组
public int[] dp;
// 记录当前找到的最小高度
int min;
// 存储所有产生最小高度树的根节点
List<Integer> list = new ArrayList<>();
// 主方法,找到所有的最小高度树的根节点
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
// 初始化深度数组和动态规划数组
deep = new int[n];
dp = new int[n];
min = n; // 初始化最小高度为n,这是一个安全的上限值
// 构建图的邻接表表示
List<Integer>[] g = new List[n];
Arrays.setAll(g, e -> new ArrayList<>());
for (int[] a : edges) {
int x = a[0];
int y = a[1];
g[x].add(y);
g[y].add(x);
}
// 第一次DFS:计算每个节点到最远叶子节点的深度
findMaxDeep(0, -1, g);
// 第二次DFS:尝试将每个节点作为根节点,并更新最小高度和根节点列表
reRoot(0, -1, g);
// 返回所有最小高度树的根节点
return list;
}
// 重新选择根节点的过程,尝试优化树的高度
public void reRoot(int i, int fa, List<Integer>[] g) {
int first = -1; // 最深的子节点深度
int second = -1; // 第二深的子节点深度
// 遍历当前节点的所有相邻节点,找出最深和第二深的深度
for (int t : g[i]) {
if (deep[t] > first) {
second = first;
first = deep[t];
} else if (deep[t] > second) {
second = deep[t];
}
}
// 更新当前节点作为根节点时的树高
dp[i] = first + 1;
// 更新最小高度和根节点列表
if (min == first + 1) {
list.add(i);
} else if (min > first + 1) {
min = first + 1;
list.clear();
list.add(i);
}
// 递归对子节点进行相同的根节点优化过程
for (int t : g[i]) {
if (dp[t] != 0)
continue;
deep[i] = (deep[t] == first ? second : first) + 1;
reRoot(t, i, g);
}
}
// 从给定节点开始DFS,计算并返回该节点到最远叶子节点的深度
public int findMaxDeep(int i, int fa, List<Integer>[] g) {
// 叶子节点检测:如果只有父节点相连,则返回深度0
if (g[i].size() == 1 && g[i].get(0) == fa) {
return 0;
}
int max = Integer.MIN_VALUE;
// 遍历所有相邻节点,找到最深的深度
for (int j = 0; j < g[i].size(); j++) {
if (g[i].get(j) == fa)
continue;
max = Math.max(max, findMaxDeep(g[i].get(j), i, g));
}
// 更新当前节点的深度并返回
deep[i] = max + 1;
return deep[i];
}
}
详解
换根动态规划(DP)在这个场景中主要是为了找出所有可能的最小高度树的根节点。代码通过两次深度优先搜索(DFS)来实现这一目标。第一次DFS计算每个节点作为根时子树的最大深度,第二次DFS则尝试重新选择每个节点作为根节点,并计算新的树高度。
换根时的核心公式和逻辑可以基于以下步骤总结:
第一次DFS - 计算每个节点的最大深度
对于每个节点u
,计算其作为根节点的子树最大深度deep[u]
:
deep[u] = max(deep[v] + 1) for all v ∈ children of u
这里,deep[v]
表示节点v
的子树最大深度,v
是u
的子节点。+1
代表从u
到其任一子节点v
的边。
第二次DFS - 重新选择根节点并更新深度
在尝试将根节点从u
更换到其任一子节点v
时,需要更新v
及其子树的最大深度。这里涉及到两个关键的动态规划数组:deep[]
和dp[]
。deep[]
在第一次DFS中计算并存储了每个节点作为根节点时的最大深度,而dp[]
用于存储在考虑根节点更换时各个节点的更新深度。
当从节点u
向节点v
更换根节点时,u
成为了v
的一个子节点,因此需要根据u
的深度更新v
的深度。在代码中,这一步通过比较deep[t]
值来实现,其中first
和second
分别记录了u
的最大和次大子树深度。基于first
和second
的值,我们可以为u
和v
计算新的最大深度:
- 如果
v
是u
的最大深度贡献子节点,则u
的新深度应该是second + 1
。 - 如果
v
不是贡献最大深度的子节点,u
的新深度仍然是first + 1
。
- 更新
dp[i]
(即节点i
作为根时的树高)考虑当前节点i
的所有子节点,基于子节点的deep[]
值计算i
的dp[]
值。特别地,如果当前dp[i]
小于已经找到的min
值,则更新min
并重置结果列表list
。