这里是题目描述:LeetCode-310.最小高度树
想要找到由给定的图构成的最小高度树的根节点,我们可以删除图中 “最外围” 的节点;删除操作后,又有一部分之前不处于 “最外围” 的节点成为新的“最外围节点”;因此不断重复执行 “删除最外围节点” 这一操作,直到只剩下位于原图 “最中心” 位置的节点,它们就是我们所寻找的最小高度树的根节点
那么,如何循环地确定并删除最外围的节点呢?我们借助解决拓扑排序问题的思路,当然也有所不同,先说明我们的思路:
统计给定图中所有节点的“度”;将当前度为1的节点,就是最外围的节点存入一个队列;将队列中的节点出队列,将它们从图中删除并将与它们相连的还未进队列的节点的“度”减1;队列中的节点删除完毕后,再次将图中新的“度为1”也就是新的最外围的节点从图中删除并入队列,如此循环操作,直到,只剩下一个或多个“最中心”的节点最晚入队列,它们就是我们所寻找的最小高度树根节点。注意,我们每次需要一次性将当前的所有在队列中的度为1的节点出队列并删除,然后再重新将经过“度减法”成为新的最外围的元素统一入队列;而不是在出队列的同时随时就将新的最外围节点入队列,因为这样将导致无法确定最中心节点
我们在解决本题时借助了解决拓扑排序问题的思路,并借助队列来存储“最外围节点”,并在出队列的同时不断确定下一批进队列的节点,因此也可以说借助了官渡有限搜索的思想。但是我们问题及解法依然和拓扑排序问题及解法有一些不同点:
- 本题给定的是一个无向无环图,拓扑排序给的是有向无环图
- 我们统计“度”为1的节点进队列,并将它们一次性出队列并删除,然后再次统计新的“度”为1的点进队列;拓扑排序统计“入度”为0的点进队列,并且在出队列的过程中可以随时将新的“入度”为0的点进队列,不要求一次性清空队列
- 本题图中最终剩下“位于最中心”位置的节点;拓扑排序最终剩下“最尽头”位置的点
题解代码:
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
if(n<=1)
{
List<Integer> list=new ArrayList<>();
list.add(n-1);
return list;
}
boolean[] inQueue=new boolean[n]; //记录一个节点是否已经进队列
int numIn=0; //记录已经进队列的节点个数
boolean[][] graph=new boolean[n][n]; //用二位矩阵表示图
int[] degree=new int[n]; //记录各个节点的度
for(int i=0;i<edges.length;i++)
{
graph[edges[i][0]][edges[i][1]]=true;
graph[edges[i][1]][edges[i][0]]=true;
degree[edges[i][0]]+=1;
degree[edges[i][1]]+=1;
}
Queue<Integer> queue=new LinkedList<>(); //让度为1的节点进队列
for(int i=0;i<degree.length;i++)
{
if(degree[i]==1)
{
queue.offer(i);
inQueue[i]=true;
numIn+=1;
}
}
while(numIn<n)
{
Queue<Integer> tempQueue=new LinkedList<>();
while(!queue.isEmpty()) //一次性把队列中的所有节点出队列
{
int o=queue.remove();
for(int i=0;i<graph[o].length;i++)
{
if(graph[o][i] && !inQueue[i])
{
degree[i]-=1; //相应节点的度减1
if(degree[i]<=1) //度被减为1时,进入临时队列
{
tempQueue.offer(i);
inQueue[i]=true;
numIn+=1;
}
}
}
}
queue=tempQueue; //临时队列中的节点进入队列,进行下一轮出队列
}
List<Integer> res=new ArrayList<>();
while(!queue.isEmpty()) //最后一轮队列中的度为1的节点就是所求结果
{
res.add(queue.remove());
}
return res;
}
}