SWE · Coding

树上所有节点距离和:一遍子树统计,一遍换根

从一个根的答案出发,沿父子边用n−2×子树大小更新。

题目重点:从一个根的答案出发,沿父子边用n−2×子树大小更新。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 节点0..n−1,无向连通无环树,边无权;返回每个根到全部节点的边数距离之和。
  • n=0且无边返回空列表;非法边、重复或不连通输入拒绝。

解题路线

  1. 从0构建parent/order/depth。sum(depth)是根0答案;逆序order累加子树size。使用显式遍历避免长链递归栈。
  2. 从u换根到子v,v子树的size[v]个节点距离各减1,其余n−size[v]个各加1,因此ans[v]=ans[u]+n−2size[v]。
  3. 正序遍历确保父答案已知。每个节点只参与常数次工作,避免对每个根重新BFS的O(n²)。

手动走一遍

路径0−1−2−3:ans0=6,size1=3,因此ans1=6+4−6=4;接着ans2=4、ans3=6。星形中心0、三片叶:中心距离和3,每叶5。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

def distance_sums(n, edges):
    if n == 0:
        if edges:
            raise ValueError("invalid empty tree")
        return []
    if len(edges) != n - 1:
        raise ValueError("tree requires n-1 edges")
    graph = [[] for _ in range(n)]
    for u, v in edges:
        if not 0 <= u < n or not 0 <= v < n or u == v:
            raise ValueError("invalid edge")
        graph[u].append(v)
        graph[v].append(u)
    parent = [-2] * n
    parent[0] = -1
    order, depth = [0], [0] * n
    for u in order:
        for v in graph[u]:
            if v == parent[u]:
                continue
            if parent[v] != -2:
                raise ValueError("cycle or repeated edge")
            parent[v] = u
            depth[v] = depth[u] + 1
            order.append(v)
    if len(order) != n:
        raise ValueError("disconnected")
    size = [1] * n
    for v in reversed(order[1:]):
        size[parent[v]] += size[v]
    answer = [0] * n
    answer[0] = sum(depth)
    for v in order[1:]:
        answer[v] = answer[parent[v]] + n - 2 * size[v]
    return answer

复杂度

O(n)时间和O(n)空间,包含邻接表、parent/order/size/答案;不是仅输出数组之外O(1)。

易错点与追问

容易失分的边界

  • 树公式不可直接应用一般图,因为去边后未必分成两个组件。
  • 无权树的−1/+1变化,带权w时应改为w×(n−2size[v])。

继续深挖

  • 节点有权重时用子树权重替代size,公式怎样写?
  • 若边权动态更新,多次查询所有答案或单根答案各有什么代价?
✓已复制微信号!