力扣101次双周赛 图中最短环

在这里插入图片描述
在这里插入图片描述

首先使用拓扑排序处理无向图的方法,去掉所有不成环的枝叶,就会出现类似示例1的情况;
观察到数据量只有 1000 ,可以接受 n 2 n^2 n2 的复杂度。对剩下的每个点,使用bfs来算出,从当前点出发又回到当前点的最短距离,也就是该点所处的最小环大小;
记住细节,把拓扑排序的访问数组 vis 传给 bfs ,防止遍历已经被剪掉的枝叶,同时 bfs 的队列的节点不止维护当前被遍历的点,还要记住这个点来自哪个父节点,防止往回走,形成一个不合法的长度为 1 的答案;

class Solution {
    List<Integer>[] child;
    
    public int bfs(int begin,boolean[] vis){
        int[] v = new int[child.length];
        Queue<int[]> q = new ArrayDeque();
        q.offer(new int[]{begin,-1});
        Arrays.fill(v,-1);
        v[begin] = 0;
        int len = 1;
        int ret = 0;
        loop : while(!q.isEmpty()){
            int size = q.size();
            for(int i = 0;i < size;i++){
                int[] tmp = q.poll();
                int now = tmp[0];
                int fa = tmp[1];
                for(int c : child[now]){
                    if(vis[c]) continue;
                    if(v[c] != -1){
                        // if(begin == 0)
                        //     System.out.println(c + " " + v[c] + " " + now + " " + v[now]);
                        if(c != fa){
                            ret = v[c] + v[now] + 1;
                            break loop;
                        }else continue;
                    }
                    v[c] = len;
                    q.offer(new int[]{c,now});
                }
            }
            len++;
        }
        return ret;
    }
    public int findShortestCycle(int n, int[][] edges) {
        int[] cnt = new int[n];
        child = new List[n];
        for(int i = 0;i < n;i++) child[i] = new ArrayList();
        for(int[] e : edges){
            cnt[e[0]]++;
            cnt[e[1]]++;
            child[e[0]].add(e[1]);
            child[e[1]].add(e[0]);
        }
        Queue<Integer> q = new ArrayDeque();
        boolean[] vis = new boolean[n];
        int remain = n;
        for(int i = 0;i < n;i++)
            if(cnt[i] <= 1){
                vis[i] = true;
                q.offer(i);
                remain--;
            }
        while(!q.isEmpty()){
            int id = q.poll();
            for(int c : child[id]){
                if(vis[c]) continue;
                if(--cnt[c] <= 1){
                    vis[c] = true;
                    q.offer(c);
                    remain--;
                }
                // System.out.println(c + " " + remain);
            }
        }   
        if(remain == 0) return -1;
        int res = Integer.MAX_VALUE;
        for(int i = 0;i < n;i++){
            
            if(!vis[i]) res = Math.min(res,bfs(i,vis));
        }
        return res;
    }
}

另外,我们发现一个环内的每个点,会被重复计算多次这点可以优化,几乎 n 2 n^2 n2 可以优化为 n n n,在找成环的最终两点的过程中,持续记录每个点的父节点,找到后再回溯对每个父节点进行标记,表示这个同一个环的点可以不用再次求解。
注意:和访问数组一样,在入队列的时候,就把访问标记和父亲标记给写好,不然很容易出问题;

class Solution {
    List<Integer>[] child;
    boolean[] pass;
    public int bfs(int begin,boolean[] vis){
        int[] v = new int[child.length];
        int[] f = new int[child.length];
        Queue<int[]> q = new ArrayDeque();
        q.offer(new int[]{begin,-1});
        f[begin] = -1;
        Arrays.fill(v,-1);
        v[begin] = 0;
        pass[begin] = true;
        int len = 1;
        int ret = 0;
        int[] pair = new int[2];
        loop : while(!q.isEmpty()){
            int size = q.size();
            for(int i = 0;i < size;i++){
                int[] tmp = q.poll();
                int now = tmp[0];
                int fa = tmp[1];
                for(int c : child[now]){
                    if(vis[c]) continue;
                    if(v[c] != -1){
                        // if(begin == 0)
                        //     System.out.println(c + " " + v[c] + " " + now + " " + v[now]);
                        if(c != fa){
                            ret = v[c] + v[now] + 1;
                            pair[0] = c;pair[1] = now;
                            break loop;
                        }else continue;
                    }
                    v[c] = len;
                    q.offer(new int[]{c,now});
                    f[c] = now;
                }
            }
            len++;
        }
        // System.out.println(pair[0] + " " + pair[1]);
        while(pair[0] != -1){
            pass[pair[0]] = true;
            // System.out.println(pair[0]);
            pair[0] = f[pair[0]];
        }
        while(pair[1] != -1){
            pass[pair[1]] = true;
            pair[1] = f[pair[1]];
        }
        return ret;
    }
    public int findShortestCycle(int n, int[][] edges) {
        int[] cnt = new int[n];
        child = new List[n];
        for(int i = 0;i < n;i++) child[i] = new ArrayList();
        for(int[] e : edges){
            cnt[e[0]]++;
            cnt[e[1]]++;
            child[e[0]].add(e[1]);
            child[e[1]].add(e[0]);
        }
        Queue<Integer> q = new ArrayDeque();
        boolean[] vis = new boolean[n];
        int remain = n;
        for(int i = 0;i < n;i++)
            if(cnt[i] <= 1){
                vis[i] = true;
                q.offer(i);
                remain--;
            }
        while(!q.isEmpty()){
            int id = q.poll();
            for(int c : child[id]){
                if(vis[c]) continue;
                if(--cnt[c] <= 1){
                    vis[c] = true;
                    q.offer(c);
                    remain--;
                }
                // System.out.println(c + " " + remain);
            }
        }   
        if(remain == 0) return -1;
        int res = Integer.MAX_VALUE;
        pass = new boolean[n];
        for(int i = 0;i < n;i++){
            if(!vis[i] && !pass[i]) res = Math.min(res,bfs(i,vis));
        }
        return res;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值