news 2026/2/27 21:07:10

算法题 树中距离之和

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法题 树中距离之和

834. 树中距离之和

问题描述

给定一个有n个节点的无向树,节点编号从0n-1。给你一个长度为n-1的二维数组edges,其中edges[i] = [ai, bi]表示树中节点aibi之间存在一条边。

返回一个长度为n的数组answer,其中answer[i]是树中所有其他节点到节点i的距离之和。

示例

输入:n=6,edges=[[0,1],[0,2],[2,3],[2,4],[2,5]]输出:[8,12,6,10,10,10]
  • 节点0到其他节点的距离:1→1, 2→1, 3→2, 4→2, 5→2,总和=8
  • 节点1到其他节点的距离:0→1, 2→2, 3→3, 4→3, 5→3,总和=12
  • 节点2到其他节点的距离:0→1, 1→2, 3→1, 4→1, 5→1,总和=6
  • 以此类推

算法思路

两次DFS(树形DP)

  1. 核心

    • 如果暴力计算每个节点到其他所有节点的距离,时间复杂度为 O(n²)
    • 利用树的结构,可以通过换根DP优化到 O(n)
  2. 第一次DFS(后序遍历)

    • 以任意节点(如0)为根,计算:
      • subtreeSize[node]:以node为根的子树中节点数量
      • dp[node]:子树中所有节点到node的距离之和
  3. 第二次DFS(前序遍历)

    • 利用父节点的结果推导子节点的结果
    • 当从父节点u移动到子节点v时:
      • v的子树中的subtreeSize[v]个节点距离减少1
      • 其他n - subtreeSize[v]个节点距离增加1
      • answer[v] = answer[u] - subtreeSize[v] + (n - subtreeSize[v])
      • answer[v] = answer[u] + n - 2 * subtreeSize[v]

代码实现

importjava.util.*;classSolution{privateList<List<Integer>>graph;// 邻接表表示树privateint[]subtreeSize;// subtreeSize[i] 表示以i为根的子树节点数privateint[]dp;// dp[i] 表示子树中所有节点到i的距离之和privateint[]answer;// 最终答案privateintn;// 节点总数/** * 计算树中每个节点到其他所有节点的距离之和 * 使用两次DFS的换根DP * * @param n 节点数量 * @param edges 边数组 * @return 每个节点的距离和数组 */publicint[]sumOfDistancesInTree(intn,int[][]edges){this.n=n;this.graph=newArrayList<>();this.subtreeSize=newint[n];this.dp=newint[n];this.answer=newint[n];// 初始化邻接表for(inti=0;i<n;i++){graph.add(newArrayList<>());}// 构建无向图for(int[]edge:edges){graph.get(edge[0]).add(edge[1]);graph.get(edge[1]).add(edge[0]);}// 第一次DFS:以0为根,计算子树大小和dp值dfs1(0,-1);// 第二次DFS:换根计算所有节点的答案dfs2(0,-1);returnanswer;}/** * 第一次DFS(后序遍历): * 计算每个节点的子树大小和子树内距离和 * * @param node 当前节点 * @param parent 父节点(避免回溯) */privatevoiddfs1(intnode,intparent){subtreeSize[node]=1;// 至少包含自己dp[node]=0;// 初始化距离和为0// 遍历所有子节点for(intchild:graph.get(node)){if(child==parent){continue;// 跳过父节点}dfs1(child,node);// 递归处理子树// 累加子树信息subtreeSize[node]+=subtreeSize[child];// 子树中每个节点到当前节点的距离 = 到子节点的距离 + 1// 总距离 = dp[child] + subtreeSize[child]dp[node]+=dp[child]+subtreeSize[child];}}/** * 第二次DFS(前序遍历): * * @param node 当前节点 * @param parent 父节点 */privatevoiddfs2(intnode,intparent){// 当前节点的答案就是dp[node](第一次DFS已计算根节点答案)answer[node]=dp[node];// 遍历所有子节点,进行换根for(intchild:graph.get(node)){if(child==parent){continue;// 跳过父节点}// 换根:从node移动到child// 保存原始值intoriginalSubtreeSizeNode=subtreeSize[node];intoriginalSubtreeSizeChild=subtreeSize[child];intoriginalDpNode=dp[node];intoriginalDpChild=dp[child];// 更新子树大小(换根后)subtreeSize[node]=n-subtreeSize[child];subtreeSize[child]=n;// 更新dp值:answer[child] = answer[node] + n - 2 * subtreeSize[child](换根前的值)dp[child]=dp[node]-subtreeSize[child]+(n-subtreeSize[child]);// 递归处理子树dfs2(child,node);}}}

算法分析

  • 时间复杂度:O(n)
    • 两次DFS,每次遍历所有节点和边一次
    • 树有 n-1 条边,所以总时间复杂度为 O(n)
  • 空间复杂度:O(n)
    • 邻接表存储:O(n)
    • 递归栈深度:O(n)(最坏情况下树退化为链)

算法过程

输入:n = 6, edges = [[0,1],[0,2],[2,3],[2,4],[2,5]]

第一次DFS(以0为根):

dfs1(0, -1)

  • 处理子节点1:dfs1(1, 0)
    • subtreeSize[1] = 1,answer[1] = 0
  • 处理子节点2:dfs1(2, 0)
    • dfs1(3, 2)subtreeSize[3] = 1,answer[3] = 0
    • dfs1(4, 2)subtreeSize[4] = 1,answer[4] = 0
    • dfs1(5, 2)subtreeSize[5] = 1,answer[5] = 0
    • subtreeSize[2] = 1 + 1 + 1 + 1 = 4
    • answer[2] = (0+1) + (0+1) + (0+1) = 3
  • subtreeSize[0] = 1 + 1 + 4 = 6
  • answer[0] = (0+1) + (3+4) = 8

第一次DFS后:

  • subtreeSize = [6, 1, 4, 1, 1, 1]
  • answer = [8, 0, 3, 0, 0, 0]

第二次DFS(换根):

dfs2(0, -1)

  • 处理子节点1:
    • answer[1] = answer[0] + 6 - 2 * subtreeSize[1] = 8 + 6 - 2 = 12
    • dfs2(1, 0)(1没有其他子节点)
  • 处理子节点2:
    • answer[2] = answer[0] + 6 - 2 * subtreeSize[2] = 8 + 6 - 8 = 6
    • dfs2(2, 0)
      • 处理子节点3:answer[3] = 6 + 6 - 2 = 10
      • 处理子节点4:answer[4] = 6 + 6 - 2 = 10
      • 处理子节点5:answer[5] = 6 + 6 - 2 = 10

最终结果:

answer = [8, 12, 6, 10, 10, 10]

测试用例

importjava.util.Arrays;publicclassMain{publicstaticvoidmain(String[]args){Solutionsolution=newSolution();// 测试用例1:标准示例intn1=6;int[][]edges1={{0,1},{0,2},{2,3},{2,4},{2,5}};int[]result1=solution.sumOfDistancesInTree(n1,edges1);System.out.println("Test 1: "+Arrays.toString(result1));// [8, 12, 6, 10, 10, 10]// 测试用例2:两个节点intn2=2;int[][]edges2={{0,1}};int[]result2=solution.sumOfDistancesInTree(n2,edges2);System.out.println("Test 2: "+Arrays.toString(result2));// [1, 1]// 测试用例3:链状树intn3=4;int[][]edges3={{0,1},{1,2},{2,3}};int[]result3=solution.sumOfDistancesInTree(n3,edges3);System.out.println("Test 3: "+Arrays.toString(result3));// [6, 4, 4, 6]// 测试用例4:星形树intn4=5;int[][]edges4={{0,1},{0,2},{0,3},{0,4}};int[]result4=solution.sumOfDistancesInTree(n4,edges4);System.out.println("Test 4: "+Arrays.toString(result4));// [4, 6, 6, 6, 6]// 测试用例5:单节点intn5=1;int[][]edges5={};int[]result5=solution.sumOfDistancesInTree(n5,edges5);System.out.println("Test 5: "+Arrays.toString(result5));// [0]// 测试用例6:三个节点线性intn6=3;int[][]edges6={{0,1},{1,2}};int[]result6=solution.sumOfDistancesInTree(n6,edges6);System.out.println("Test 6: "+Arrays.toString(result6));// [3, 2, 3]}}

关键点

  1. 换根DP

    • 利用已计算的父节点结果快速推导子节点结果
    • 避免重复计算,将O(n²)优化到O(n)
  2. 换根公式

    • 当根从u移到v时:
      • v的子树中subtreeSize[v]个节点距离-1
      • 其余n - subtreeSize[v]个节点距离+1
      • 变化:-subtreeSize[v] + (n - subtreeSize[v]) = n - 2*subtreeSize[v]
    • 使用邻接表存储无向树
    • DFS时通过parent参数避免回溯
  3. 两次遍历

    • 第一次DFS(后序):自底向上计算子树信息
    • 第二次DFS(前序):自顶向下传播换根结果

常见问题

  1. 为什么第一次DFS后只有根节点正确?
    第一次DFS只计算了子树内的距离和,没有考虑父节点方向的节点。只有根节点没有父节点。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/25 9:12:18

M1芯片Android模拟器完全配置手册:从零开始搭建开发环境

M1芯片Android模拟器完全配置手册&#xff1a;从零开始搭建开发环境 【免费下载链接】android-emulator-m1-preview 项目地址: https://gitcode.com/gh_mirrors/an/android-emulator-m1-preview 在Apple Silicon M1芯片的Mac设备上进行Android应用开发&#xff0c;选择…

作者头像 李华
网站建设 2026/2/15 17:49:31

仅限内部流出:Open-AutoGLM沙箱环境支付拦截机制解密与绕行策略

第一章&#xff1a;Open-AutoGLM 点咖啡不自动付款 在使用 Open-AutoGLM 框架实现自动化点单功能时&#xff0c;部分用户反馈系统能够成功识别菜单并提交订单&#xff0c;但未触发自动付款流程。该问题通常出现在支付网关鉴权失败或用户账户余额校验逻辑异常的场景中。 问题排…

作者头像 李华
网站建设 2026/2/27 18:19:24

GPT-SoVITS语音合成在心理疗愈语音内容生成中的尝试

GPT-SoVITS语音合成在心理疗愈语音内容生成中的尝试 在心理咨询室的灯光下&#xff0c;一位来访者闭上眼睛&#xff0c;耳机里传来熟悉而温和的声音&#xff1a;“深呼吸……感受空气缓缓流入身体。”这声音不属于任何远程连线的真人咨询师&#xff0c;而是由AI生成的、高度还原…

作者头像 李华
网站建设 2026/2/7 9:06:26

GPT-SoVITS能否应对多人混合语音场景?分离与克隆挑战

GPT-SoVITS能否应对多人混合语音场景&#xff1f;分离与克隆挑战 在影视配音、远程会议记录或播客制作中&#xff0c;我们经常面对一个共同难题&#xff1a;如何从一段多个人同时说话的录音里&#xff0c;精准提取某位发言者的声音&#xff0c;并用它生成全新的自然语音&#x…

作者头像 李华
网站建设 2026/2/18 14:17:54

n8n工作流自动化完整指南:7天从入门到实战精通

n8n工作流自动化完整指南&#xff1a;7天从入门到实战精通 【免费下载链接】n8n n8n 是一个工作流自动化平台&#xff0c;它结合了代码的灵活性和无代码的高效性。支持 400 集成、原生 AI 功能以及公平开源许可&#xff0c;n8n 能让你在完全掌控数据和部署的前提下&#xff0c;…

作者头像 李华
网站建设 2026/2/20 3:07:12

微信群发神器:3分钟掌握高效消息分发技巧

微信群发神器&#xff1a;3分钟掌握高效消息分发技巧 【免费下载链接】WeChat-mass-msg 微信自动发送信息&#xff0c;微信群发消息&#xff0c;Windows系统微信客户端&#xff08;PC端 项目地址: https://gitcode.com/gh_mirrors/we/WeChat-mass-msg 还在为节日祝福、工…

作者头像 李华