题目
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,任何一个没有简单环路的连通图都是一棵树。
给你一棵包含 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 (节点数)较大时,会导致执行时间超时。接下来介绍一种时间复杂度更低的算法。
这里基于以下结论进行实现:
- 设节点 x 和 y 是树中距离最远的两个节点,其距离为 dist[x][y],那么最小高度 minheight = dist[x][y] /2,并且最小高度树的根节点一定在 x 到 y 的路径上(也就是路径上的中间节点)。
- 要找到 x、y,只需要从树的任意一个节点 p 出发,找到与 p 距离最远的节点 x,再从 x 出发,找到距离最远的节点 y,这就是树中距离最远的两个节点(x、y)。
这里直接给出了结论,证明方法感兴趣的话可以自行上网查阅。
此外需要注意的是,对于树中最长路径上的节点数 count 为奇数和偶数要作不同的处理:
- 如果为奇数,那么根节点取 path[count / 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 在线编程学习平台,集在线做题、编程竞赛、即时通讯、文章创作、视频教程、技术论坛为一体
公众号
有兴趣可以关注公众号一起学习更多的干货哈!