力扣每日一题0406-310. 最小高度树

150 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情 。

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

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

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

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

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

image.png

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

示例 2:

image.png

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

拓扑排序

由于树的高度由根节点到叶子节点之间的最大距离构成,假设树中距离最长的两个节点为 (x,y)(x,y),它们之间的距离为 maxdist=dist[x]\textit{maxdist} = \textit{dist}[x],假设 xxyy 的路径为xp1p2pk1pkyx \rightarrow p_1 \rightarrow p_2 \rightarrow \cdots \rightarrow p_{k-1} \rightarrow p_k \rightarrow y,根据方法一的证明已知最小树的根节点一定为该路径中的中间节点,我们尝试删除最外层的度为 1 的节点 x,yx,y 后,则可以知道路径中与 x,yx,y 相邻的节点 p1,pkp1,pk 此时也变为度为 1 的节点,此时我们再次删除最外层度为 1 的节点直到剩下根节点为止。

实际做法如下:

  • 首先找到所有度为 1 的节点压入队列,此时令节点剩余计数 remainNodes=n\textit{remainNodes} = n
  • 同时将当前 remainNodes\textit{remainNodes} 计数减去出度为 1 的节点数目,将最外层的度为 1 的叶子节点取出,并将与之相邻的节点的度减少,重复上述步骤将当前节点中度为 1 的节点压入队列中;
  • 重复上述步骤,直到剩余的节点数组 remainNodes2\textit{remainNodes} \le 2 时,此时剩余的节点即为当前高度最小树的根节点。
var findMinHeightTrees = function(n, edges) {
    const ans = [];
    if (n === 1) {
        ans.push(0);
        return ans;
    }
    const degree = new Array(n).fill(0);
    const adj = new Array(n).fill(0).map(() => new Array());
    for (const edge of edges) {
        adj[edge[0]].push(edge[1]);
        adj[edge[1]].push(edge[0]);
        degree[edge[0]]++;
        degree[edge[1]]++;
    }
    const queue = [];
    for (let i = 0; i < n; i++) {
        if (degree[i] === 1) {
            queue.push(i);
        }
    }
    let remainNodes = n;
    while (remainNodes > 2) {
        const sz = queue.length;
        remainNodes -= sz;
        for (let i = 0; i < sz; i++) {
            const curr = queue.shift();
            for (const v of adj[curr]) {
                degree[v]--;
                if (degree[v] === 1) {
                    queue.push(v);
                }
            }
        }
    }
    while (queue.length) {
        ans.push(queue.shift());
    }
    return ans;
};

复杂度分析

时间复杂度:O(n)O(n)O(n),其中 nnn 是为节点的个数。图中边的个数为 n−1n-1n−1,因此建立图的关系需要的时间复杂度为 O(n)O(n)O(n),通过广度优先搜索需要的时间复杂度为 O(n+n−1)O(n + n - 1)O(n+n−1),求最长路径的时间复杂度为 O(n)O(n)O(n),因此总的时间复杂度为 O(n)O(n)O(n)。

空间复杂度:O(n)O(n)O(n),其中 nnn 是节点的个数。由于题目给定的图中任何两个顶点都只有一条路径连接,因此图中边的数目刚好等于 n−1n-1n−1,用邻接表构造图所需的空间刚好为 O(2×n)O(2 \\times n)O(2×n),存储每个节点的距离和父节点均为 O(n)O(n)O(n),使用广度优先搜索时,队列中最多有 nnn 个元素,所需的空间也为 O(n)O(n)O(n),因此空间复杂度为 O(n)O(n)O(n)。
  • 时间复杂度:O(n)O(n),其中 n 是为节点的个数。图中边的个数为 n1n−1,因此建立图的关系需要的时间复杂度为 O(n)O(n),通过广度优先搜索需要的时间复杂度为 O(n+n1)O(n + n - 1),求最长路径的时间复杂度为 O(n)O(n),因此总的时间复杂度为 O(n)O(n)
  • 空间复杂度:O(n)O(n),其中 nn 是节点的个数。由于题目给定的图中任何两个顶点都只有一条路径连接,因此图中边的数目刚好等于 n1n−1,用邻接表构造图所需的空间刚好为 O(2×n)O(2 \times n),存储每个节点的距离和父节点均为 O(n)O(n),使用广度优先搜索时,队列中最多有 nn 个元素,所需的空间也为 O(n)O(n),因此空间复杂度为 O(n)O(n)

image.png