leetcode 685. 冗余连接 II dfs+链式前向星 或者 并查集

在本问题中,有根树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。每一个节点只有一个父节点,除了根节点没有父节点。
输入一个有向图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组。 每一个边 的元素是一对 [u, v],用以表示有向图中连接顶点 u and v和顶点的边,其中父节点u是子节点v的一个父节点。
返回一条能删除的边,使得剩下的图是有N个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

  • 思路:链式前向星存图,我们记录每个点的入度。利用dfs判断有没有环,如果有环,那么最后一条访问的边即为答案,若没有环,那么直接找入度为2的点,从后往前遍历一遍edge数组即可。
class Solution {
    public class Edge{ 
        int to;
        int next;
    }
    public static int[] deg;
    public static Edge[] g;
    public static int cnt;
    public static int[] head;
    public static boolean[] vis;
    public static int[] res;
    public void add(int u,int v){ //存图
        g[cnt]=new Edge();
        g[cnt].to=v;
        g[cnt].next=head[u];
        head[u]=cnt++;
    }
    public boolean dfs(int st,int pre){ //下一个点 前一个点 即(v,u)
        if(vis[st]){ //若再次访问的话 即为环的最后一条边 为答案
            res=new int[]{pre,st};
            return true;
        }
        vis[st]=true; //设为访问
        for(int i=head[st];i!=-1;i=g[i].next){ //遍历该点的所有边
            int to=g[i].to;
            if(dfs(to,st)){
                return true;
            }
        }
        vis[st]=false; 
        return false;
    }
    public int[] findRedundantDirectedConnection(int[][] edges) {
        if(edges==null||edges[0].length==0){
            return new int[2];
        }
        int n=edges.length;
        cnt=0;
        g=new Edge[n+1];
        deg=new int[n+1];
        head=new int[n+1];
        vis=new boolean[n+1];
        java.util.Arrays.fill(head,-1);
        for(int i=0;i<n;++i){
            int u=edges[i][0];
            int v=edges[i][1];
            add(u,v); //建图
            deg[v]++; //入度
        }
        int st=0;
        for(int i=1;i<=n;++i){ //判断起点 若没有入度为0的点 就从1开始
            if(deg[i]==0){
                st=i;
                break;
            }
        }
        if(st==0){
            st=1;
        }
        boolean flag=dfs(st,0);
        if(flag){ //说明成环 有答案
            return res;
        }else{
            int t=-1;
            for(int i=1;i<=n;++i){ //找入度为2的点
                if(deg[i]==2){
                    t=i;
                }
            }
            for(int i=n-1;i>=0;--i){ //从后往前遍历
                if(edges[i][1]==t){
                    return edges[i];
                }
            }
        }
        return new int[2];
    }
}

思路二: 并查集

主要分三种情况:

  • 有环且有入度为2的点
  • 有环但是没有入度为2的点
  • 无环但是有入度为2的点

注意题目中的图是有向图,因此不是形成闭环就一个环了,还需要考虑到方向。
在这里插入图片描述
主要做法就是利用并发集来找环,比冗余连接一多出来的步骤就是我们要记录下入度为2的点的相关的边。

public class _685_1 {
	// 并查集
    private int[] union;
    // 记录入度为2的点
    private int[] parent;
    // 往上一直查找父亲结点
    private int find(int x){
        return union[x] == x ? x : (union[x] = find(union[x]));
    }
    public int[] findRedundantDirectedConnection(int[][] edges) {
        if(edges == null || edges[0].length == 0){
            return new int[2];
        }
        int n = edges.length;
        union = new int[n + 1];
        parent = new int[n + 1];
        // 初始化
        for (int i = 1; i <= n; i++) {
            union[i] = i;
        }
        // 成环标记
        int[] isRing = null;
        int[] e1 = null;
        int[] e2 = null;
        for (int[] pair : edges){
            int u = pair[0];
            int v = pair[1];
            // 入度为2
            if(parent[v] != 0){
                // 记录入度为2的点的两条边
                e1 = new int[]{parent[v], v};
                e2 = pair;
            }else {
                // 入度为1
                parent[v] = u;
                // 并查集合并
                int x = find(u);
                int y = find(v);
                if(x != y){
                    union[x] = y;
                } else {
                    // 找到环
                    isRing = pair;
                }
            }
        }
        if(e1 != null && e2 != null){
         	// 入度为2 情况分有环或者无环  
            return isRing == null ? e2 : e1;
        }else {
        	// 成环 无入度为2的点
            return isRing;
        }
    }
}

在区分三种情况的时候, 我们可以结合图来分析,这样思路会更加清晰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值