树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 n 个节点的数,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。
可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。
请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。
树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。
leetcode 310. 最小高度树
示例 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 * 104
- edges.length == n - 1
- 0 <= ai, bi < n
- ai != bi
- 所有 (ai, bi) 互不相同
- 给定的输入 保证 是一棵树,并且 不会有重复的边
方法一:bfs
统计各节点的度,然后把所有出度为1的节点进队列,即先将叶子结点入队列,然后不断地bfs,最后找到的就是由两边的叶子结点同时向中间靠近的节点,那么这个中间节点就相当于把整个树的最大直径平分了。
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
//邻接表
vector<vector<int>> adj(n);
//入度表
vector<int> degree(n, 0);
//结果集
vector<int> res;
//初始化邻接表,统计入度
for(auto edge : edges){
adj[edge[0]].push_back(edge[1]);
adj[edge[1]].push_back(edge[0]);
degree[edge[0]]++;
degree[edge[1]]++;
}
queue<int> qu;
//将入度为1的叶子结点入队
for(int i=0; i<n; i++){
if(degree[i] == 1){
qu.push(i);
}
}
//特殊情况处理
if(n == 1) res.push_back(0);
//从外向内一层一层剥,每次加入的都是树的一层叶子,
//最后一层即为最终结果
while(!qu.empty()){
int num = qu.size();
res.clear();
//将一层的叶子加入结果集,但在此之前先清空结果集
for(int i=0; i<num; i++){
int first = qu.front();
qu.pop();
res.push_back(first);
for(int w : adj[first]){
degree[w]--;
//入度减为1的,变成叶子,入队
if(degree[w] == 1){
qu.push(w);
}
}
}
}
return res;
}
};
方法二:dfs
在评论区看到的,题解中大部分都是bfs,这个dfs方法看了大半天才看明白,害~
思路:利用树的直径,找到中间点。
任意一点x出发,找到距离x最远的a点,然后从a点出发,找到距离a点最远的b点, 找到a和b之间的中点,即为所求。很明显可能是1个或者两个点
class Solution {
public:
//邻接表
vector<vector<int>> adj;
//用来标记0 ~ n-1, n个节点分别到某个节点x的距离,
vector<int> range;
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
adj.resize(n);
range.resize(n, 0);
//用来存放a和b之间的节点的辅助数组
vector<int> help;
for(auto edge : edges){
adj[edge[0]].push_back(edge[1]);
adj[edge[1]].push_back(edge[0]);
}
//第一次从0开始搜索,所以将0到0的距离置为1,以0为原点
//用两点之间节点的数量表示两点的距离
range[0] = 1;
int a = dfs(0, range);
//重置range的每一项为0
fill(range.begin(), range.end(), 0);
//第二次从a开始
range[a] = 1;
//此时,range是以a为原点,各个点距离a点的距离数组
int b = dfs(a, range);
help.push_back(b);
//令a不动,b逐渐向a靠近
while(a != b){
for(int ans : adj[b]){
//如果相邻的某一点ans距离a的距离 = b距离a的距离-1
//说明a,ans,b在同一条线上
if(range[ans]+1 == range[b]){
b = ans;
help.push_back(b);
//邻接表中应该只有一个满足
break;
}
}
}
vector<int> res;
int mid;
//辅助数组的size是2的倍数,中间点有两个
if(help.size()%2 == 0){
mid = help.size()/2;
res.push_back(help[mid-1]);
res.push_back(help[mid]);
}
else{
mid = help.size()/2;
res.push_back(help[mid]);
}
return res;
}
//深度搜索以v为根节点的树,距离v点最远的点
int dfs(int v, vector<int>& range){
int max = v;
for(int w : adj[v]){
//range[w]==0,表明还未被搜索过
if(range[w] == 0){
//因为是邻接点,所以在根节点的基础上+1
range[w] = range[v]+1;
//以w为根节点,因为range[v]!=0 ,相当于断开v-w这条边
int other = dfs(w, range);
if(range[other] > range[max]){
max = other;
}
}
}
return max;
}
};
答案整理,来源于题解和评论区。