这题本质并不复杂, 只是操作起来略烦和需要小心
一个图是否为树,判断条件有二:1.是否存在环。2.是否0 ~ n - 1这n个点都被走完了,也就是木有orphan nodes
所以整个流程基本就是:根据输入建图,然后遍历图找环,无环而且走过的节点数为n即是树。
构图的方式很多种,可以自己开一个class。如果懒得这么做,一般用哈希表也可以HashMap<Integer(父亲节点),HashSet<Integer>(儿子节点群)>表示。题目也给你提示了,输入是根据无向图的结构进行输入的,也就是一条边等同于有向图的两条边,ex. [0, 1] 等同于 0 -> 1, 1 -> 0。同样的,也因为如此,遍历树的时候,判断是否为环的时候要忽略长度为1的环。也就是0 -> 1 -> 0这样的环可以忽略。
根据上述描述,可以得到代码如下:
public boolean validTree(int n, int[][] edges) {
HashMap<Integer, HashSet<Integer>> directed = new HashMap<>();
for (int[] edge : edges) {
if (!directed.containsKey(edge[0])) directed.put(edge[0], new HashSet<Integer>());
if (!directed.containsKey(edge[1])) directed.put(edge[1], new HashSet<Integer>());
directed.get(edge[0]).add(edge[1]);
directed.get(edge[1]).add(edge[0]);
}
HashSet<Integer> visited = new HashSet<>();
if (!_hasNoCycle(directed, visited, -1, 0)) return false;
return visited.size() == n;
}
private boolean _hasNoCycle(HashMap<Integer, HashSet<Integer>> directed, HashSet<Integer> visited, int parent, int current) {
if (visited.contains(current)) {
return false;
} else {
visited.add(current);
HashSet<Integer> children = directed.get(current);
if (children == null) return true;
for (int child : children) {
if (child == parent) continue;
if (!_hasNoCycle(directed, visited, current, child)) {
return false;
}
}
return true;
}
}
其中if(children == null) return true主要针对的是n = 1的时候只有一个0节点的edge case,这个其实也是一颗合法的树。
2022-07-31 updated:
这一题上面的先建图再走图的解法在leetcode上面performance只能击败百分之十几,所以我猜是有别的更优的解法了。然后一下子就找到了,嗯,在lc的solution里面。。。
这一题其实可以解释为两个充要条件,这是看到solution之后我才明白的。
1.边的数目,必然是节点的数目减一
2.所有节点全联通。
前面这个事情好验证,后面这个,方法其实也很多,其实也可以走建图这个道路,然后看看是不是有节点没有链接。
LC的solution里面给出的方案比较有意思,是union find。其实也说得通,union find是比较有效率的,将一堆节点联通到一个图(并不一定是数)的方式和数据结构。因为已经有1作为此图是树的保障,只需要再用union find保证他们全联通即可。 给出代码如下:
class Solution {
int[] nodes;
// The boolean is to make sure that A and B are not already in the same graph
// Otherwise they are causing the circle which breaks the definition of a tree.
private boolean union(int A, int B) {
int rootA = find(A);
int rootB = find(B);
if (rootA == rootB) {
return false;
}
nodes[rootA] = rootB;
return true;
}
private int find(int A) {
while (nodes[A] != A) {
A = nodes[A];
}
return A;
}
private void initialize(int n) {
nodes = new int[n];
for (int i = 0; i < nodes.length; i++) {
nodes[i] = i;
}
}
public boolean validTree(int n, int[][] edges) {
// 1. Condition 1, number of edges equals with n - 1
if (edges.length != n - 1) {
return false;
}
initialize(n);
for (int[] edge : edges) {
// Since the problem promises that there is no self loop or duplicate edges
// Which means that if union returns false, edge[0] and edge[1] are already
// causing a loop (in terms of undirected graph)
// Like 1->2->3 and 1->3 it will lead to 1->2->3->1 in an undirected graph.
if (!union(edge[0], edge[1])) {
return false;
}
}
return true;
}
}