【算法学习笔记】换根DP -- java代码实现

1. 问题描述

给定一个无向、连通的树。树中有 n 个标记为 0…n-1 的节点以及 n-1 条边 。
给定整数 n 和数组 edges , edges[i] = [ a i a_i ai, b i b_i bi]表示树中的节点 a i a_i ai b i b_i bi 之间有一条边。
返回长度为 n 的数组 answer ,其中 answer[i] 是树中第 i 个节点与所有其他节点之间的距离之和。

1.1 例子说明

在这里插入图片描述
输入: n = 6, edges = [[0,1],[0,2],[2,3],[2,4],[2,5]]
输出: [8,12,6,10,10,10]
解释: 树如图所示。我们可以计算出 dist(0,1) + dist(0,2) + dist(0,3) + dist(0,4) + dist(0,5)
也就是 1 + 1 + 2 + 2 + 2 = 8。 因此,answer[0] = 8,以此类推。

2. 思路

一开始看到往往想所有节点都做一遍根,然后各自深度优先遍历一下,这样做的时间复杂度 O ( n 2 ) O(n^2) O(n2),那能不能有什么方法优化一下呢,这时候就引出要讲的换根DP算法:
在这里插入图片描述
上面的图片分别是以0和2作为根节点,然后到其余各个节点的距离和,我们可以发现当改变根时候只有新根及其子树会减少路径,而其余节点会相应增加路径(下移了)
所以我们可以得到 ans[2] = ans[0] + 2 - 4 ,此时我们就可以省去很多的计算时间,但是需要注意的是这种方法需要得到每一个节点的子树!!,这个我们可以在第一遍dfs的时候做


将上式进行一个抽象我们就可以得到递推方程

  1. 假设当前根节点是x,而下一个做根节点的是y。
  2. 假设第 i 个节点的子树记录在size数组中(非i节点的所有子树就是 n-size[i] )。则有:

ans[y] = ans[x] +n - size[y] - size[y]
           ~~~~~~~~~~            = ans[x] +n - 2*size[y]
即为核心公式!!

2.2 算法流程

  1. 从 0 出发进行 DFS,累加 0 到每一个点的距离,得到初始   a n s [ 0 ] ~ans[0]  ans[0]
  2. DFS同时计算子树的大小 s i z e [ i ] size[i] size[i]
  3. 第二遍从 0 出发 DFS,设 y y y x x x 的儿子,那么有: a n s [ y ] = a n s [ x ] + n − 2 ∗ s i z e [ y ] ans[y] = ans[x] +n - 2*size[y] ans[y]=ans[x]+n2size[y]
  4. 递归得到每一个   a n s [ i ] ~ans[i]  ans[i]

3. 代码实现

class Solution {

    private List<Integer>[] g;

    private int[] ans, size;



    public int[] sumOfDistancesInTree(int n, int[][] edges) {

        g = new ArrayList[n]; // g[x] 表示 x 的所有邻居

        Arrays.setAll(g, e -> new ArrayList<>());

        for (var e : edges) {

            int x = e[0], y = e[1];

            g[x].add(y);

            g[y].add(x);

        }

        ans = new int[n];

        size = new int[n];

        dfs(0, -1, 0); // 0 没有父节点

        reroot(0, -1); // 0 没有父节点

        return ans;

    }



    private void dfs(int x, int fa, int depth) {

        ans[0] += depth; // depth 为 0 到 x 的距离

        size[x] = 1;

        for (int y : g[x]) { // 遍历 x 的邻居 y

            if (y != fa) { // 避免访问父节点

                dfs(y, x, depth + 1); // x 是 y 的父节点

                size[x] += size[y]; // 累加 x 的儿子 y 的子树大小

            }

        }

    }



    private void reroot(int x, int fa) {

        for (int y : g[x]) { // 遍历 x 的邻居 y

            if (y != fa) { // 避免访问父节点

                ans[y] = ans[x] + g.length - 2 * size[y];

                reroot(y, x); // x 是 y 的父节点

            }

        }

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值