For a undirected graph with tree characteristics, we can choose any node as the root. The result graph is then a rooted tree. Among all possible rooted trees, those with minimum height are called minimum height trees (MHTs). Given such a graph, write a function to find all the MHTs and return a list of their root labels.
Format
The graph contains n
nodes which are labeled from 0
to n - 1
. You will be given the number n
and a list of undirected edges
(each edge is a pair of labels).
You can assume that no duplicate edges will appear in edges
. Since all edges are undirected, [0, 1]
is the same as [1, 0]
and thus will not appear together in edges
.
Example 1:
Given n = 4
, edges = [[1, 0], [1, 2], [1, 3]]
0 | 1 / \ 2 3
return [1]
Example 2:
Given n = 6
, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
0 1 2 \ | / 3 | 4 | 5
return [3, 4]
Note:
(1) According to the definition of tree on Wikipedia: “a tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any connected graph without simple cycles is a tree.”
(2) The height of a rooted tree is the number of edges on the longest downward path between the root and a leaf.
思路:又遇上了最痛苦的topological sort,赶紧补一下,http://blog.youkuaiyun.com/dm_vincent/article/details/7714519
其实还是比较简单的,就想着 “学某门课要先修某门课” 这个实际的例子,把indegree为0的先拿出来,也就是不需要任何先修课的课程可以先学,然后更新其他节点的indegree,因为已经修了一门了嘛,说不定可以修其他以此为基础的课程呢!
拓扑排序有2中实现方式:
(1)BFS(就是上面描述的)
(2)DFS (就是Coursera上algorithm课程老师描述的)
类似的题目:
https://leetcode.com/problems/course-schedule/?tab=Description
https://leetcode.com/problems/course-schedule-ii/?tab=Description(只记录degree计数即可)
这里当做无向图来对待:
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
/*
* 找到最长的那条path,中间的1个或者2个就是要找的
* 参考拓扑排序的BFS实现
* 如果有1条最长的path,那不断减掉degree为1的,最后剩下的就是
* 如果有2条最长的path,那他们必然相交于中点,如果不是,就必然可以找到更长的path,相交于中点的话,不断减掉degree为1的仍然是可行的
*/
public class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
if(n == 1) return Collections.singletonList(0);
// 构建degree集合,因为要最后的结果,所以不能简单用一个数组对degree计数
List<HashSet<Integer>> degree = new ArrayList<HashSet<Integer>>();
for(int i=0; i<n; i++) degree.add(new HashSet<Integer>());
for(int[] edge : edges) {
degree.get(edge[0]).add(edge[1]);
degree.get(edge[1]).add(edge[0]);
}
// 不断删除degree为0的节点,再更新剩下的节点的degree,所以用一个数组保存当前degree为0的节点
List<Integer> leaves = new ArrayList<Integer>();
for(int i=0; i<n; i++)
if(degree.get(i).size() == 1)
leaves.add(i);
// 一直往中点收缩,剩下个1就是1个,剩下2个就是2个
while(n > 2) {
n = n - leaves.size();
List<Integer> newLeaves = new ArrayList<Integer>();
// 更新degree
for(int leaf : leaves) {
for(int j : degree.get(leaf)) {
degree.get(j).remove(leaf);
// 不可能出现degree为0,
if(degree.get(j).size() == 1)
newLeaves.add(j);
}
}
leaves = newLeaves;
}
return leaves;
}
}
二刷:
看了一下第一次刷写的博客,感觉完全不一样,还是要多刷,不然都找不到题目的感觉
这次刷因为有一刷的概念,直接想到了从两边收索找中点的思路,写了以下:
package l310;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/*
* last case TLE
*/
public class ArrayBFS {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
int[] degree = new int[n];
boolean[][] adj = new boolean[n][n];
for(int[] edge : edges) {
degree[edge[0]] ++;
degree[edge[1]] ++;
adj[edge[0]][edge[1]] = true;
adj[edge[1]][edge[0]] = true;
}
int cnt = 0;
while(cnt < n-2) {
Set<Integer> s = new HashSet<Integer>();
for(int i=0; i<n; i++)
if(degree[i] == 1) {
degree[i] = -1;
s.add(i);
}
for(int i : s)
for(int j=0; j<n; j++)
if(adj[i][j]) degree[j] --;
cnt += s.size();
}
List<Integer> ret = new ArrayList<Integer>();
for(int i=0; i<n; i++)
if(degree[i] >= 0) ret.add(i);
return ret;
}
}
猜想可能是因为是稀疏矩阵,用连接表可能会快一点
package l310;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/*
* last case TLE
* so we use Map or List, notice we will always have 0 .. n-1, so we just use List
* 还可以优化,求degree为1的节点的时候不需要遍历,只要每当有degree为1就加到一个Set里面(这好像也是拓扑排序的优化技巧)
*/
public class Solution {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
int[] degree = new int[n];
List<List<Integer>> adj = new ArrayList<List<Integer>>();
for(int i=0; i<n; i++) adj.add(new ArrayList<Integer>());
for(int[] edge : edges) {
degree[edge[0]] ++;
degree[edge[1]] ++;
adj.get(edge[0]).add(edge[1]);
adj.get(edge[1]).add(edge[0]);
}
int cnt = 0;
while(cnt < n-2) {
Set<Integer> s = new HashSet<Integer>();
for(int i=0; i<n; i++)
if(degree[i] == 1) {
degree[i] = -1;
s.add(i);
}
for(int i : s)
for(int j : adj.get(i))
degree[j] --;
cnt += s.size();
}
List<Integer> ret = new ArrayList<Integer>();
for(int i=0; i<n; i++)
if(degree[i] >= 0) ret.add(i);
return ret;
}
}
看到有人说暴力也可以AC,就试了一下遍历每个节点当root求树高,再取最小值
package l310;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
* TLE
*/
public class BruteForce {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
Map<Integer, List<Integer>> m = new HashMap<Integer, List<Integer>>();
for(int[] edge : edges) {
if(!m.containsKey(edge[0])) m.put(edge[0], new ArrayList<Integer>());
m.get(edge[0]).add(edge[1]);
if(!m.containsKey(edge[1])) m.put(edge[1], new ArrayList<Integer>());
m.get(edge[1]).add(edge[0]);
}
int min = Integer.MAX_VALUE;
List<Integer> ret = new ArrayList<Integer>();
ret.add(0);
for(int root : m.keySet()) {
boolean[] marked = new boolean[n];
marked[root] = true;
int h = getHeight(m, root, marked);
if(h < min) {
min = h;
ret.clear();
ret.add(root);
} else if(h == min) {
ret.add(root);
}
}
return ret;
}
private int getHeight(Map<Integer, List<Integer>> m, int root, boolean[] marked) {
int ret = 0;
for(int next : m.get(root))
if(!marked[next]) {
marked[next] = true;
ret = Math.max(ret, 1 + getHeight(m, next, marked));
marked[next] = false;
}
return ret;
}
}
也试了一下Floyd算法
package l310;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/*
* Floyd
* 某一行最大也不超过最大的一半就是可能的root
*/
public class Floyd {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
// Floyd
int[][] adj = new int[n][n];
for(int[] a : adj) Arrays.fill(a, Integer.MAX_VALUE);
for(int[] edge : edges) {
adj[edge[0]][edge[1]] = 1;
adj[edge[1]][edge[0]] = 1;
}
for(int k=0; k<n; k++)
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
if(adj[i][k]!=Integer.MAX_VALUE && adj[k][j]!=Integer.MAX_VALUE
&& i!=j && adj[i][k] + adj[k][j] < adj[i][j])
adj[i][j] = adj[i][k] + adj[k][j];
//
int[] max = new int[n];
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
if(adj[i][j] != Integer.MAX_VALUE)
max[i] = Math.max(max[i], adj[i][j]);
int allMax = 0;
for(int a : max)
if(a != Integer.MAX_VALUE) allMax = Math.max(allMax, a);
int validMax = (allMax+1)/2;
List<Integer> ret = new ArrayList<Integer>();
for(int i=0; i<n; i++)
if(max[i] <= validMax) ret.add(i);
return ret;
}
}
但是这些暴力解答都没有AC