算法题分享 | 最小高度树

29 阅读5分钟

Ipad壁纸分享_4_-来自精灵谷的惭愧锦鲤_来自小红书网页版.jpg

题目

树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,任何一个没有简单环路的连通图都是一棵树。

给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。

可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。

请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。

树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。

 

示例 1:

输入: n = 4, edges = [[1,0],[1,2],[1,3]]
输出: [1]
解释: 如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。

示例 2:

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

 

提示:

  • 1 <= n <= 2 * 10⁴
  • edges.length == n - 1
  • 0 <= ai, bi < n
  • ai != bi
  • 所有 (ai, bi) 互不相同
  • 给定的输入 保证 是一棵树,并且 不会有重复的边

题解

解题思路

最直观的方法是求出以每个节点作为根节点的树的高度,从中取出最小值。这种方法可行,但是当 n (节点数)较大时,会导致执行时间超时。接下来介绍一种时间复杂度更低的算法。

这里基于以下结论进行实现:

  1. 设节点 x 和 y 是树中距离最远的两个节点,其距离为 dist[x][y],那么最小高度 minheight = dist[x][y] /2,并且最小高度树的根节点一定在 x 到 y 的路径上(也就是路径上的中间节点)。
  2. 要找到 x、y,只需要从树的任意一个节点 p 出发,找到与 p 距离最远的节点 x,再从 x 出发,找到距离最远的节点 y,这就是树中距离最远的两个节点(x、y)。

这里直接给出了结论,证明方法感兴趣的话可以自行上网查阅。

此外需要注意的是,对于树中最长路径上的节点数 count 为奇数和偶数要作不同的处理:

  1. 如果为奇数,那么根节点取 path[count / 2]
  2. 如果为偶数,那么有两个满足要求的根节点,分别为 path[count / 2] 和 path[count / 2 - 1]

在本文代码实现中,直接从节点 0 出发,找到距离最远的节点 x , 再从 x 出发,找到距离最远的 y。要找到距离最远的节点,可使用深度优先搜索或广度优先搜索,这里两种实现均有给出。在搜索过程中,要记录路径上的节点,可以使用 parent[n] 数组来记录每个节点对应的上一个节点,这样最终就能得到目标路径上的所有节点。此外,在搜索过程中需要避免重复处理节点,例如在广度优先搜索中,可以使用 visit[n] 数组标识节点是否被处理过。

代码

深度优先搜索

class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {

        List<Integer> ans = new ArrayList<>();

        // 若只有一个节点 直接返回即可
        if (n == 1) {
            ans.add(0);
            return ans;
        }
        // 整理各个节点的边
        List<Integer>[] adj = new List[n];
        for (int i = 0; i < n; i++) {
            adj[i] = new ArrayList<Integer>();
        }
        for (int[] edge : edges) {
            adj[edge[0]].add(edge[1]);
            adj[edge[1]].add(edge[0]);
        }
        // 初始化 parent 数组,用于保存节点间路径信息,例如 parent[1] = 0, 表示在路径中,节点 1 的上一个节点是 0
        int[] parent = new int[n];
        Arrays.fill(parent, -1);
        // 找到和节点 0 距离最远的节点 x
        int x = findLongestNode(0, adj, parent);
        // 找到和节点 x 距离最远的节点 y
        int y = findLongestNode(x, adj, parent);
        // 通过 parent 获取到 x 到 y 路径上的所有节点
        List<Integer> path = new ArrayList<>();
        parent[x] = -1;
        int t = y;
        path.add(y);
        while(parent[t] != -1) {
            t = parent[t];
            path.add(t);
        }
        // 得到符合要求的根节点集合
        int count = path.size();
        if (count % 2 == 0) {
            ans.add(path.get(count/ 2 - 1));
        }
        ans.add(path.get(count / 2));
        return ans;
    }

    int findLongestNode(int start, List<Integer>[] adj, int[] parent) {
        
        // 初始化 dist 数组,用于保存 start 节点到各节点的距离
        int n = adj.length;
        int[] dist = new int[n];
        Arrays.fill(dist, -1);
        // 此处注意别忘了初始化 到自身的距离为 0
        dist[start] = 0;
        // 调用 dfs 深度优先遍历, 获取 dist 数据
        dfs(start, adj, parent, dist);
        // 通过 dist 得到距离最远的节点
        int maxDist = 0;
        int index = 0;
        for (int i = 0; i < dist.length; i++) {
            if (maxDist < dist[i]) {
                maxDist = dist[i];
                index = i;
            }
        }
        return index; 
    }

    void dfs(int cur, List<Integer>[] adj, int[] parent, int[] dist) {
        
        for(int v : adj[cur]) {
            if (dist[v] == -1) {
                dist[v] = dist[cur] + 1;
                parent[v] = cur;
                dfs(v, adj, parent, dist);
            }
        }
    }
}

复杂度分析

  • 时间复杂度:O(n)
    其中 n 为树的节点数。
  • 空间复杂度:O(n)

广度优先搜索

class Solution {
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {

        List<Integer> ans = new ArrayList<>();

        // 若只有一个节点 直接返回即可
        if (n == 1) {
            ans.add(0);
            return ans;
        }

        List<Integer>[] adj = new List[n];
        for (int i = 0; i < n; i++) {
            adj[i] = new ArrayList<>();
        }

        for (int[] edge : edges) {
            adj[edge[0]].add(edge[1]);
            adj[edge[1]].add(edge[0]);
        }

        int[] parent = new int[n];
        Arrays.fill(parent, -1);

        int x = findLongestNode(0, adj, parent);
        int y = findLongestNode(x, adj, parent);

        List<Integer> path = new ArrayList<>();

        parent[x] = -1;
        while(y != -1) {
            path.add(y);
            y = parent[y];
        }

        int len = path.size();
        if (len % 2 == 0) {
            ans.add(path.get(len / 2 - 1));
        }
        ans.add(path.get(len / 2));

        return ans;
    }

    int findLongestNode(int start, List<Integer>[] adj, int[] parent) {
        Queue<Integer> queue = new ArrayDeque<>();
        boolean[] visit = new boolean[parent.length];
    
        queue.offer(start);
        visit[start] = true;

        int longestNode = start;

        while(!queue.isEmpty()) {
            int cur = queue.poll();
            longestNode = cur;
            for(int i : adj[cur]) {
                if (!visit[i]) {
                    parent[i] = cur;
                    queue.offer(i);
                    visit[i] = true;
                }
            }
        }

        return longestNode;
    }
}

复杂度分析

  • 时间复杂度:O(n)
    其中 n 为树的节点数。
  • 空间复杂度:O(n)

优质项目推荐

推荐一个可用于练手、毕业设计参考、增加简历亮点的项目。

lemon-puls/txing-oj-backend: Txing 在线编程学习平台,集在线做题、编程竞赛、即时通讯、文章创作、视频教程、技术论坛为一体

公众号

有兴趣可以关注公众号一起学习更多的干货哈!

扫码_搜索联合传播样式-白色版.png