leetcode 834. Sum of Distances in Tree(树中的距离和)

在这里插入图片描述
无向连接的树(不一定是二叉树),求每个节点到其他节点的距离和。
返回一个数组,数组的第i个元素就是第i个节点到其他所有节点的距离之和。

思路:

涉及无向图的构造和遍历,树的前序后序遍历,问题拆分

能想到的最笨的办法就是以每个节点为root进行DFS遍历,这样就能求到每个节点到其他所有节点的距离。但是这样要遍历n次。

如何能在O(n)时间内解决?

先看个例子,看节点0到其他所有节点的距离

    0
   / \
  1   2
 / \
3   4

0到1,2的距离都是1,1到3,4的距离是1,
而0到3,4的距离则在1到3,4的距离基础上加1,因为又深了一层,

现在要求的是距离之和,
那么0到3,4的距离之和 = 2(1到3,4的距离之和) + 1* 2(3,4是2个节点,每个节点加深了一层,距离+1)

那么0 (root) 到所有节点的距离之和呢?
就 = 0到它直接连接的1,2的距离之和2 + 1到它直接连接的3,4距离和 + 3,4节点数
= 左子树的距离和(1 (左子树的root) 到它直接连接的3,4的距离和+左子树的节点数(1,3,4共3个))
+ 右子树的距离和(2 (右子树的root) 到它直接连接的null的距离和 + 右子树的节点数(只有2一个))

以此类推,一层一层往下。

能推出这步非常关键,推出这个式子就相当于解决了问题!

所以需要知道以每个节点为root的子树有多少个节点
是一个从下到上遍历的过程,
所以用后序遍历,把结果保存在nodeNums数组。

遍历完之后,我们能得到节点0到其他所有节点的距离之和。

那其他节点怎么办,以1为例来说明。
之前我们求了以1为root的子树的节点数,是3个(1,3,4),

distance_sum[0] = 0到1 + 0到3 + 0到4 + 0到2
而distance_sum[1] = 1到1 + 1到3 + 1到4 + 1到2
可以看到1,3,4都在以1为root的子树中,这3个节点到1的距离比到0的距离少了1,
而这个子树之外的节点0和2,到1的距离比到0的距离多了1,

那么distance_sum[1] = distance_sum[0] - nodeNums[1]*1 + (n - nodeNums[1]) * 1,
这样一层一层往下走,就能得到所有节点的distance_sum[i],
这是一个从上到下遍历的过程,所以用到树的前序遍历

无向图的遍历中需要有flag记录节点是不是已经被访问过,
而现在是树,不会有环的出现,但是因为边是双向的,只需要看当前节点是不是又回到上一节点即可。

class Solution {
    ArrayList<Integer>[] graph;
    int[] dis;
    int[] nodeNums;

    public int[] sumOfDistancesInTree(int n, int[][] edges) {
        dis = new int[n];
        nodeNums = new int[n];
        graph = new ArrayList[n];

        for(int i = 0; i < n; i++) graph[i] = new ArrayList<Integer>();

        //make the graph
        for(int[] edge : edges) {
            graph[edge[0]].add(edge[1]);
            graph[edge[1]].add(edge[0]);
        }

        postOrder(0, -1);  //get node numbers
        preOrder(0, -1, n);   //get distance

        return dis;
    }

    //获取以每个node为root的子树的节点数
    void postOrder(int root, int pre) {
        //left & right for binary tree
        //这里不一定是二叉树,可能是多叉树
        for(Integer node : graph[root]) {
            if(node == pre) continue;
            postOrder(node,root);
            nodeNums[root] += nodeNums[node]; 
            dis[root] += dis[node] + nodeNums[node];
        }
        nodeNums[root] ++; //节点数加上root自己
    }

    void preOrder(int root, int pre, int n) {
        for(Integer node : graph[root]) {
            if(node == pre) continue;
            //dis[root]现在是正确的,而和root连接的点到root的距离要-1,
            //以node为root的子树中的所有节点都需要距离-1,
            //所以dis[node] = dis[root]-1*nodeNums[node]
            //但同时,以node为root的子树之外的其他节点到node的距离要+1
            //这个过程是自上而下的
            dis[node] = dis[root] - nodeNums[node] + n - nodeNums[node];
            preOrder(node, root, n);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝羽飞鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值