LeetCode2024年3月17日每日一题(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的子树最大深度,vu的子节点。+1代表从u到其任一子节点v的边。

第二次DFS - 重新选择根节点并更新深度

在尝试将根节点从u更换到其任一子节点v时,需要更新v及其子树的最大深度。这里涉及到两个关键的动态规划数组:deep[]dp[]deep[]在第一次DFS中计算并存储了每个节点作为根节点时的最大深度,而dp[]用于存储在考虑根节点更换时各个节点的更新深度。

当从节点u向节点v更换根节点时,u成为了v的一个子节点,因此需要根据u的深度更新v的深度。在代码中,这一步通过比较deep[t]值来实现,其中firstsecond分别记录了u的最大和次大子树深度。基于firstsecond的值,我们可以为uv计算新的最大深度:

  1. 如果vu的最大深度贡献子节点,则u的新深度应该是second + 1
  2. 如果v不是贡献最大深度的子节点u的新深度仍然是first + 1
  • 更新dp[i](即节点i作为根时的树高)考虑当前节点i的所有子节点,基于子节点的deep[]值计算idp[]值。特别地,如果当前dp[i]小于已经找到的min值,则更新min并重置结果列表list
  • 22
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值