代码随想录算法训练营第六十天| 108. 冗余连接、109. 冗余连接II

[KamaCoder] 108. 冗余连接

[KamaCoder] 108. 冗余连接 文章解释

题目描述

树可以看成是一个图(拥有 n 个节点和 n - 1 条边的连通无环无向图)。 

现给定一个拥有 n 个节点(节点标号是从 1 到 n)和 n 条边的连通无向图,请找出一条可以删除的边,删除后图可以变成一棵树。

输入描述

第一行包含一个整数 N,表示图的节点个数和边的个数。

后续 N 行,每行包含两个整数 s 和 t,表示图中 s 和 t 之间有一条边。

输出描述

输出一条可以删除的边。如果有多个答案,请删除标准输入中最后出现的那条边。

输入示例
3
1 2
2 3
1 3
输出示例
1 3

图中的 1 2,2 3,1 3 等三条边在删除后都能使原图变为一棵合法的树。但是 1 3 由于是标准输出里最后出现的那条边,所以输出结果为 1 3

数据范围:

1 <= N <= 1000.

[KamaCoder] 108. 冗余连接

自己看到题目的第一想法

    无.

看完代码随想录之后的想法

    并查集的基础理论的基础应用, 基础篇中的基础应用篇.

import java.util.Scanner;

public class Main {
    private static int[] father;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            init(scanner.nextInt());
            int fatherNode;
            int childNode;
            for (int i = 1; i < father.length; i++) {
                fatherNode = scanner.nextInt();
                childNode = scanner.nextInt();
                if (isSame(fatherNode, childNode)) {
                    System.out.println(fatherNode + " " + childNode);
                    return;
                }
                join(fatherNode, childNode);
            }
            System.out.println();
        }
    }
    
    private static void init(int nodeCount) {
        father = new int[nodeCount + 1];
        for (int i = 1; i <= nodeCount; i++) {
            father[i] = i;
        }
    }
    
    private static boolean isSame(int u, int v) {
        return find(u) == find(v);
    }
    
    private static int find(int u) {
        if (u == father[u]) {
            return u;
        }
        return father[u] = find(father[u]);
    }
    
    private static void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) {
            return;
        }
        father[v] = u;
    }
}

自己实现过程中遇到哪些困难 

    无.

[KamaCoder] 109. 冗余连接II

[KamaCoder] 109. 冗余连接II 文章解释

题目描述

有向树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。有根树拥有 n 个节点和 n - 1 条边。 

输入一个有向图,该图由一个有着 n 个节点(节点编号 从 1 到 n),n 条边,请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一颗有向树。

输入描述

第一行输入一个整数 N,表示有向图中节点和边的个数。 

后续 N 行,每行输入两个整数 s 和 t,代表 s 节点有一条连接 t 节点的单向边

输出描述

输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。

输入示例
3
1 2
1 3
2 3
输出示例
2 3

在删除 2 3 后有向图可以变为一棵合法的有向树,所以输出 2 3

数据范围:

1 <= N <= 1000.

[KamaCoder] 109. 冗余连接II

自己看到题目的第一想法

    时隔两周, 几乎忘记了要怎么解决了. 一开始想, 不管有没有方向, 只要发现两个节点连接上了, 如果尝试再连接一次, 就是有问题. 做法就是, 假如有一条 s1 -》 t1 的边, 这时候就将 s1 和 t1 连接在同一个集合, 只要之后的 s -> t 没有在同一个集合就添加进来, 并判断一下在 father 数组里是否出现死循环. 然而判断死循环会比较麻烦一些. 当然这个想法我没有去验证行不行, 因为直接看了文章解释, 所以就跳过自己的想法了.

看完代码随想录之后的想法

    这一题比我想象的复杂一些. 破坏树结构一共有三种方式, 一种是整棵树成一个环, 一种是环外还有其他的路连接到环上, 一种是没有环但是有某个节点入度超过1. 对于环外还有其他路连接到环上, 着也是入度超过1的情况.

    因此:

        1. 需要先记录下所有的边, 同时在记录所有边的过程中, 统计每条有向边对应的目标节点的入度.

        2. 根据题意只有一条边造成了有向树被破坏, 因此如果存在入度为 2 的节点, 则指向这个节点的两条边中, 删除其中一条即可恢复有向树.

        3. 第 “2” 步中, 如果存在入度为 2 的节点, 删除进入该节点的其中一条边后如果不存在环, 则说明删除有效, 否则则需要删除另外一条边. 而判断环需要使用到并查集. 因为如果存在环的话, 连接尾首的那条边, 在并查集中就会发现首尾两个节点已经在集合中, 并返回发现环.

        4. 第 “2” 步中, 如果不存在入度为 2 的节点, 则存在环了, 这时候需要通过并查集找出形成环的最后一条边, 删除这条边即可.

import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;

public class Main {
    
    private static List<int[]> edges = new ArrayList<>();
    private static int[] indegree;
    
    // ====> 并查集部分 开始 <====
    private static int nodeCount;
    private static int[] father;
    // ====> 并查集部分 结束 <====
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            nodeCount = scanner.nextInt();
            indegree = new int[nodeCount + 1];
            int fromNode;
            int toNode;
            for (int i = 0; i < nodeCount; i++) {
                fromNode = scanner.nextInt();
                toNode = scanner.nextInt();
                edges.add(new int[]{fromNode, toNode});
                indegree[toNode]++;
            }
        }
        
        // 保存的是边的索引, 后加入的边在前
        // 根据题意, 最多只有一个节点入度为 2, 且入度最大为 2
        List<Integer> twoDegress = new ArrayList<>();
        int[] edge = null;
        for (int i = edges.size() - 1; i >= 0; i--) {
            edge = edges.get(i);
            if (indegree[edge[1]] != 2) {
                continue;
            }
            twoDegress.add(i);
        }
        if (twoDegress.size() > 0) {
            if (isTreeAfterRemoveEdge(twoDegress.get(0))) {
                edge = edges.get(twoDegress.get(0));
            } else {
                edge = edges.get(twoDegress.get(1));
            }
            System.out.println(edge[0] + " " + edge[1]);
            return;
        }
        
        getRemoveEdge();
    }
    
    private static boolean isTreeAfterRemoveEdge(int edgeIndex) {
        init();
        int[] edge = null;
        for (int i = 0; i < edges.size(); i++) {
            if (edgeIndex == i) {
                continue;
            }
            edge = edges.get(i);
            if (isSame(edge[0], edge[1])) {
                return false;
            }
            join(edge[1], edge[0]);
        }
        return true;
    }
    
    private static void getRemoveEdge() {
        init();
        int[] edge;
        for (int i = 0; i < edges.size(); i++) {
            edge = edges.get(i);
            if (isSame(edge[0], edge[1])) {
                // System.out.println("222 " + edge[0] + " " + edge[1]);
                return;
            }
            
            join(edge[1], edge[0]);
        }
    }
    
    private static void init() {
        // 并查集数据初始化
        if (father == null) {
            father = new int[nodeCount + 1];
        }
        for (int i = 1; i <= nodeCount; i++) {
            father[i] = i;
        }
    }
    
    private static boolean isSame(int u, int v) {
        return find(u) == find(v);
    }
    
    private static int find(int u) {
        if (u == father[u]) {
            return u;
        }
        return father[u] = find(father[u]);
    }
    
    private static void join(int u, int v){
        u = find(u);
        v = find(v);
        if (u == v) {
            return;
        }
        father[v] = u;
    }
}

自己实现过程中遇到哪些困难

    无. 尝试着想一些其他的解决方案, 比如一旦发现成环, 则开始删除边, 但是对于具体要删除哪条边, 变得不容易判断.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值