- 最小高度树
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 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]
示例 3:
输入:n = 1, edges = []
输出:[0]
示例 4:
输入:n = 2, edges = [[0,1]]
输出:[0,1]
提示:
1 <= n <= 2 * 104
edges.length == n - 1
0 <= ai, bi < n
ai != bi
所有 (ai, bi) 互不相同
给定的输入 保证 是一棵树,并且 不会有重复的边
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer> ans = new ArrayList();
// 排除极端情况
if (n <= 2) {
for (int i = 0; i < n; i++) {
ans.add(i);
}
return ans;
}
// 由外向里进行BFS 将度为1的所有边的节点逐个删除
// 需要一个记录入度数的数组
int[] inDegree = new int[n];
// 需要一个邻接数组
Set<Integer>[] adj = new Set[n];
for (int i = 0; i < n; i++) {
adj[i] = new HashSet();
}
// 用边数据 填充 邻接数组
for (int[] edge : edges) {
adj[edge[0]].add(edge[1]);
adj[edge[1]].add(edge[0]);
inDegree[edge[0]]++;
inDegree[edge[1]]++;
}
// 记录剩余的节点数目 ,当队列中入队一个批次结束后判断剩余个数课用于终止
int resi = n;
boolean[] del = new boolean[n];
// 记录元素的状态:是否被删除,逻辑上元素在出队时应被删除
Queue<Integer> que = new ArrayDeque();
// 将入度数为 1 的节点 入队, 同时将相邻节点的记录的该节点删除
for (int i = 0; i < n; i++) {
if (adj[i].size() == 1) {
que.offer(i); // 入队 无法 实际可以做到在出队的时候入队
}
}
while (resi > 2) { //最小高度树的结果最多有两个,当resi<=2时,可以终止
int num = que.size();
resi -= num; // 剩余元素计数 批次出队时剩余元素减少
for (int i = 0; i < num; i++) { // 出队, 出队的必要性来自于能否提供入队操作
int out = que.poll();
inDegree[out]--;
del[out] = true; // 删除元素为true
for (int val : adj[out]) { // 将出队节点的邻接节点的入度和边减少
adj[val].remove(out);
inDegree[val]--;
if (inDegree[val] == 1) {
// 必须意识到:在操作过程中,入度为1的元素必定是入队出队造成的
que.offer(val); // 入度为1 说明是下一次入队元素或者是最终结果
// 在resi >= 3的同时,使得最终结果的入度为 1 的情况是不可能的
// 所以 若 val 是最终结果,则必有此时 resi <= 2
}
}
}
}
for (int i = 0; i < n; i++) {
if (!del[i]) {
ans.add(i);
}
}
return ans;
// BFS 对所有的节点,记录每一个节点为根时的最大深度,
// 如果节点有小于之前节点的就将之前的所有节点的值去除
// 如果后续节点的深度和之前相同,则加入该后续节点
// int minHeight = Integer.MAX_VALUE; // 记录当前根节点任意时刻的最小深度
// Deque<Integer> que = new ArrayDeque(); // 使用双端队列作为队列
// List<Integer> res = new ArrayList(); // 存放(符合条件的根节点)结果
// for (int i = 0; i < n; i++) {
// // 遍历n次
// boolean[] marked = new boolean[n]; // 每次遍历都要重新开始标记是否访问
// que.offerLast(i);
// marked[i] = true;
// int deep = 0;
// while (que.size() > 0) {
// int count = que.size(); // 当前层需要出队的所有的节点数目
// deep++;
// for (int j = 0; j < count; j++){
// // 当前层的所有的元素的子节点都要入队。全部替换成下一层的元素即可。
// int cur = que.pollFirst();
// // 去边数组中找
// for (int[] edge : edges) {
// if (edge[0] == cur || edge[1] == cur) {
// int other = (edge[0] == cur ? edge[1] : edge[0]);
// if (!marked[other]) {
// que.offerLast(other);
// marked[other] = true;
// }
// }
// }
// }
// }
// if (deep < minHeight) {
// res = new ArrayList();
// res.add(i);
// minHeight = deep;
// } else if (deep == minHeight) {
// res.add(i);
// }
// }
// return res;
}
}