题目链接
思路:深搜
分析:这个题目要找最小高度树,要达到最小高度,要处理的其实是最长路径,如果我们找到了最长的路径,那么只要取中间的节点当作根节点就是最小高度树,因为最长的路径被中分了,那么也就可以做到高度最小了。
也就是说,在这个图里面找到一根最长的线,那么,你怎么折这根线使得折完以后,这根最长的线最短。那么肯定是对折,对吧。
那么现在有两个问题:
- 如果找到这个图里面的最长路径,也就是如何找到这根最长的线。
- 找到最长路径后,如何找到这个路径的中点。
**问题1:**我们可以这么想,把自身当作里面的一个点,先从自己出发,找到距离自己最远的点,这个点肯定是最长路径的一个端点。简单证明一下:如下图。
设AB就是要寻找的那个最长路径,当然,这里的A,B两个点目前是不知道的,但是肯定存在这样两个点确定这条最长的路径。
设当前点为C,
设BP=x,AP=t,PC=Y。
也就是说t+x肯定是所有路径里面最长的路径。
那么,我们假设,距离C最远的点是D点,如下图。
假设DP=s,如果存在D点,距离C最远
即y+s>y+max(t,x);
即 s>max(t,x);
max(t,x)=t或者x
如果max(t,x)=x
那么t+s>t+x
如果max(t,x)=t
x+s>x+t
那么就违背了t+x是最长的路径的前提
所以不存在这样的D点,使得C距离最远的点是D。
所以距离C点最远的点肯定是A,B中的一点
所以从任意C点出发,最远的点肯定是最长路径的一个端点。
证明完毕。
那么我们找到了最长路径的一个端点后,再从这个点出发,距离最远的点肯定是这个最长路径的另外一个点。
那么也就找到了最长路径。(因为我们找到了两个端点,两点确定一条直线,不对,曲线把,就那个意思,理解一下)。
到此问题一解决。
**问题二:**找到最长路径后,如果找到中点。这里是将这条最长路径经过的每个节点记录下来,那么直接取中点就行。
从一个节点开始去寻找最远节点的时候,采用的是广度优先遍历,可以用一个数组parentArr来记录每个节点的父节点。
即parentArr[i]=x:表示节点i的父节点是x。
当我们找到最长路径的一个端点,从这个端点出发,去找另一个端点的时候,也就是把这个端点当作根节点,去找最远的叶节点。那么这一趟我们记录好每个节点的父节点即可。根节点的父节点记作-1.
那么当我们找到另一个端点的时候,从这个端点开始,反向一层层找父节点,一直找到根节点为止,路径也就找到了。
代码实现:
class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer> ans = new ArrayList<>();
if(n==1){
ans.add(0);
return ans;
}
//list[x]:表示以x为出发点可以直接达到的点的链表,也就是邻接链表
List<Integer>[] list=new ArrayList[n];
for(int i=0;i<n;i++){
list[i]=new ArrayList<>();
}
for(int[] edge:edges){
list[edge[0]].add(edge[1]);
list[edge[1]].add(edge[0]);
}
int[] parentArr = new int[n];
Arrays.fill(parentArr,-1);
int x=findLongPathBfs(0,list,n,parentArr);
//将当前节点当作根节点,根节点的父节点为-1
parentArr[x]=-1;
//y就是另一个端点
int y=findLongPathBfs(x,list,n,parentArr);
//x->y最长
//path记录从y出发到x经过的每个点
List<Integer> path=new ArrayList<>();
//寻找从y走到x的路径
while(parentArr[y]!=-1){
path.add(y);
y=parentArr[y];
}
path.add(y);
int accountPoint = path.size();
//如果最长的路径中有奇数个点,那么最小高度数的根节点就是最中间的点
ans.add(path.get(accountPoint/2));
//如果最长的路径中有偶数个点,那么最小高度数的根节点有两个
if((accountPoint%2)==0){
ans.add(path.get(accountPoint/2-1));
}
return ans;
}
//广度优先遍历 从start开始,从邻接矩阵中寻找距离最远的点
public int findLongPathBfs(int start, List[] list, int n, int[] parentArr){
Queue<Integer> parent = new LinkedList<>();
boolean[] vis = new boolean[n];
parent.offer(start);
//用来记录最远的节点
int maxLenPoint=start;
//广度优先遍历
while(!parent.isEmpty()){
int cur=parent.poll();
maxLenPoint=cur;
vis[cur]=true;
if(list[cur]!=null){
List<Integer> children = list[cur];
for(Integer child : children){
if(!vis[child]){
parent.offer(child);
parentArr[child] = cur;
}
}
}
}
return maxLenPoint;
}
}