在问 · 算法题解

310. 最小高度树

中等图论拓扑排序(逐层剥离叶子)力扣原题 ↗

题目描述

树是一种连通且没有环的无向图,任意两个节点之间只有一条路径。

输入节点数 n,节点编号为 0 到 n - 1,以及包含 n - 1 条无向边的数组 edges,edges[i] = [ai, bi] 表示节点 ai 和 bi 相连。

可以挑任意一个节点当根。以某节点为根时,树的高度是从根往下走到叶子的最长路径上的边数。在所有选法中,高度最小的那些树称为最小高度树。

返回所有最小高度树的根节点编号,顺序不限。

示例 1
输入:n = 4, edges = [[1,0],[1,2],[1,3]]
输出:[1]

解释:节点 1 与其余三个点都直接相连,以 1 为根时高度为 1,这是唯一的最优根。

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

解释:以 3 或 4 为根时高度都是 2,其他根都更高。

数据范围

  • 1 <= n <= 2 * 10^4
  • edges.length == n - 1
  • 0 <= ai, bi < n
  • ai != bi
  • 所有 (ai, bi) 两两不同。
  • 输入保证构成一棵树,不含重复的边。

解题思路:拓扑排序(逐层剥离叶子)

最小高度树的根就是树的“中心”,即最长路径的中点。像剥洋葱一样从外往里削:每轮把当前所有叶子(度为 1 的节点)一起删掉,并更新邻居的度;邻居度变成 1 就成为下一轮的叶子。剩下不超过 2 个节点时,它们就是答案。

为什么正确

每轮同时删去所有叶子,剩下的树的中心不变;最长路径两端各缩短 1。

复杂度分析

时间 O(n),空间 O(n)。

易错点

必须一轮一轮地删(按层),不能边删边把新叶子加入当前轮,否则会削过中心。n = 1 时没有叶子,要单独返回 [0]。循环条件是 remaining > 2,因为中心可能有 2 个。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
        if n == 1:
            return [0]
        adj = [set() for _ in range(n)]
        for a, b in edges:
            adj[a].add(b)
            adj[b].add(a)
        leaves = [i for i in range(n) if len(adj[i]) == 1]
        remaining = n
        while remaining > 2:
            remaining -= len(leaves)
            new_leaves = []
            for leaf in leaves:
                nb = adj[leaf].pop()
                adj[nb].remove(leaf)
                if len(adj[nb]) == 1:
                    new_leaves.append(nb)
            leaves = new_leaves
        return leaves

载入编辑器,自己跑一遍 →

其他解法

拓扑剥离从外向内一层层删叶子,最后剩下的 1–2 个点就是中心,不需要显式找直径;两次 BFS 先找出直径再取中点。两者本质都是在找直径的中心,复杂度相同。

解法 2:两次 BFS 求直径取中点 时间 O(n),空间 O(n)

最小高度树的根一定是树的最长路径(直径)的中点。先从任意节点 0 做 BFS 找到最远点 x,再从 x 做 BFS 找到最远点 y,并记录父节点,还原 x 到 y 的直径路径。路径点数为奇数时返回中间一个点,为偶数时返回中间两个点。

把问题转化成经典的树直径,结论清晰,只用两次普通 BFS。需要理解“两次 BFS 求直径”和“中心在直径中点”的证明,还要额外还原路径。

from collections import deque

class Solution:
    def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
        adj = [[] for _ in range(n)]
        for a, b in edges:
            adj[a].append(b)
            adj[b].append(a)

        def bfs(src):
            parent = [-1] * n
            seen = [False] * n
            seen[src] = True
            q = deque([src])
            last = src
            while q:
                last = q.popleft()
                for v in adj[last]:
                    if not seen[v]:
                        seen[v] = True
                        parent[v] = last
                        q.append(v)
            return last, parent

        x, _ = bfs(0)
        y, parent = bfs(x)
        path = []
        while y != -1:
            path.append(y)
            y = parent[y]
        m = len(path)
        if m % 2:
            return [path[m // 2]]
        return [path[m // 2 - 1], path[m // 2]]

图论 专题的其他题目

查看 图论 全部题目 →