leetcode
leetcode 1001 ~ 1050
树的直径

树的直径

难度:

标签:

题目描述

代码结果

运行时间: 43 ms, 内存: 20.2 MB


/*
 * Leetcode 1245: Tree Diameter
 * 
 * 思路:
 * 1. 将树转换为图,并使用DFS进行两次遍历。
 * 2. 第一次DFS找到从任意节点开始的最远节点A。
 * 3. 第二次DFS从节点A开始,找到最远的节点B,
 * 这样A到B的路径就是树的直径。
 */

import java.util.*;
import java.util.stream.*;

class Solution {
    private int maxDiameter = 0;
    
    public int treeDiameter(int[][] edges) {
        // 构建图
        Map<Integer, List<Integer>> graph = Arrays.stream(edges)
                .flatMap(edge -> Stream.of(new int[][]{{edge[0], edge[1]}, {edge[1], edge[0]}}))
                .collect(Collectors.groupingBy(
                    e -> e[0], 
                    Collectors.mapping(e -> e[1], Collectors.toList())
                ));
        
        // 第一次DFS
        int[] firstDFS = dfs(0, -1, graph);
        
        // 第二次DFS
        int[] secondDFS = dfs(firstDFS[1], -1, graph);
        
        return secondDFS[0];
    }
    
    private int[] dfs(int node, int parent, Map<Integer, List<Integer>> graph) {
        return graph.getOrDefault(node, Collections.emptyList()).stream()
                .filter(neighbor -> neighbor != parent)
                .map(neighbor -> dfs(neighbor, node, graph))
                .max(Comparator.comparingInt(result -> result[0]))
                .map(result -> new int[]{result[0] + 1, result[1]})
                .orElse(new int[]{0, node});
    }
}

解释

方法:

该题解利用了广度优先搜索(BFS)算法来计算无向树的直径。解题思路包括:1) 构建无向图的邻接表表示。2) 从任意一个结点(这里选择了结点0)开始进行一次BFS,找到距离这个结点最远的结点,记为farthest_node。3) 从farthest_node再进行一次BFS,找到从这个点出发能到达的最远距离,即为树的直径。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在无向树的直径计算中,选择从任意结点开始进行第一次BFS就能得到最远的结点?是否有可能选择的起始结点会影响到找到的最远结点?
在无向树中,从任意节点开始的第一次BFS能找到最远的节点,是因为树是一个连通且无环的特殊图结构,任意两点间有且仅有一条路径。进行一次BFS会遍历所有节点,并最终到达某个叶节点。由于任意节点到叶节点的路径可能是最长路径的一部分,所以第一次BFS能确保找到一个端点。选择不同的起始节点,最终找到的最远节点可能不同,但这不影响第二次BFS找到的树的直径长度,因为树的直径是定义为任意两点间的最长距离,而这两点必定是树叶。
🦆
在题解中提到从farthest_node再进行一次BFS可以得到树的直径,这种方法是否在所有类型的树结构中都有效?例如,在非常不平衡的树中也是如此吗?
这种方法在所有类型的树结构中都有效。无论树的形态如何(包括非常不平衡的树),树的直径都是从一个叶节点到另一个叶节点的最长路径。通过从任一节点找到的最远节点(必然是一个叶节点)开始第二次BFS,可以保证无论树的形态如何,都能找到另一个最远的叶节点,从而正确计算出树的直径。
🦆
题解中提到使用邻接表来表示图,为什么选择邻接表而不是邻接矩阵,尤其是在处理树这种特殊形式的图时?
选择邻接表而不是邻接矩阵的主要原因是空间效率。树中的节点数与边数几乎相等(N个节点有N-1条边),使用邻接矩阵表示会创建一个N×N的矩阵,大多数位置为空,导致空间浪费。而邻接表只存储每个节点的直接邻居,空间复杂度是O(N),更适合稀疏图,如树。此外,使用邻接表可以更快地迭代节点的邻居,提高算法效率。
🦆
在BFS过程中,题解使用了队列以及已访问节点集合来控制节点访问,这种方法是否最优,还有没有其他方式可以优化BFS的执行效率?
使用队列和已访问节点集合是BFS的标准实现,以确保每个节点仅被访问一次,避免重复访问造成无限循环。这种方法在大多数情况下已经是最优的,特别是在处理树这种没有环的结构时。然而,对于特定类型的数据或环境,可以考虑一些优化措施,例如使用更快的队列实现(如双端队列),或在内存使用上进行优化(例如使用位向量来标记已访问的节点)。在多核或并行环境下,可以考虑并行BFS算法以进一步提高效率。

相关问题