[Leetcode] 261. Graph Valid Tree

这题本质并不复杂, 只是操作起来略烦和需要小心
一个图是否为树,判断条件有二: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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值