1. 卡码网 108.冗余连接
题目链接:https://kamacoder.com/problempage.php?pid=1181
文章链接:https://www.programmercarl.com/kamacoder/0108.冗余连接.html
题目描述
有一个图,它是一棵树,他是拥有 n 个节点(节点编号1到n)和 n - 1 条边的连通无环无向图(其实就是一个线形图),如图:
现在在这棵树上的基础上,添加一条边(依然是n个节点,但有n条边),使这个图变成了有环图,如图:
先请你找出冗余边,删除后,使该图可以重新变成一棵树。
输入描述
第一行包含一个整数 N,表示图的节点个数和边的个数。
后续 N 行,每行包含两个整数 s 和 t,表示图中 s 和 t 之间有一条边。
输出描述
输出一条可以删除的边。如果有多个答案,请删除标准输入中最后出现的那条边。
思路:
每输入一条边需要判断是否在同一个集合中,若是,则该边是冗余边,添加则成环;若否,则添加。
注意:由于题目中仅仅添加了一条边,则一旦出现就是最后的冗余边,不会出现多个冗余边的情况。
java
import java.util.*;
public class Main {
static int[] father;
static int a;
static int b;
public static void main (String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 节点个数
father = new int[n+1];
init(); // 初始化
// for (int i=0;i<n;i++) {
// int v = sc.nextInt();
// int u = sc.nextInt();
// if (isSame(u,v)) {
// a=v;
// b=u;
// continue;
// }
// join(u,v);
// }
// System.out.println(a + " " + b);
for (int i=0;i<n;i++) {
int v = sc.nextInt();
int u = sc.nextInt();
if (isSame(u,v)) {
System.out.println(v + " " + u);
return;
}
join(u,v);
}
}
public static void init() {
for (int i=1;i<father.length;i++) {
father[i]=i;
}
}
public static void join(int u,int v) {
u = find(u);
v = find(v);
if (u==v) return; // 若相等,则表示已经在同一个集合里面了
father[v] = u;
}
public static int find(int u) {
if (u == father[u]) return u;
return father[u] = find(father[u]);
}
public static boolean isSame(int u,int v) {
u = find(u);
v = find(v);
return u == v;
}
}
2. 卡码网 109.冗余连接II
题目链接:https://kamacoder.com/problempage.php?pid=1182
文章链接:https://www.programmercarl.com/kamacoder/0109.冗余连接II.html
题目描述
有一种有向树,该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。有向树拥有 n 个节点和 n - 1 条边。如图:
现在有一个有向图,有向图是在有向树中的两个没有直接链接的节点中间添加一条有向边。如图:
输入一个有向图,该图由一个有着 n 个节点(节点编号 从 1 到 n),n 条边,请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一颗有向树。
输入描述
第一行输入一个整数 N,表示有向图中节点和边的个数。
后续 N 行,每行输入两个整数 s 和 t,代表这是 s 节点连接并指向 t 节点的单向边
输出描述
输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。
思路:
依题目可知:有向树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
而有向图是在有向树中的两个没有直接链接的节点中间添加一条有向边。该有向边可能形成环,也有可能不形成环,仅仅是给有向树添加一条有向边而已。
另外要注意:只添加一条边。
会出现以下几种情况:
1️⃣存在某个节点的入度为2,即有两条有向边指向该节点。如下图。
此时需要分别判断删这两个边中的一个边之后是不是有向树,若是,则返回该边;否则,返回另一个边。如果是删哪个都可以,优先删顺序靠后的边。
对于并查集:将所有边的两端节点分别加入并查集,遇到要删除的边则跳过,如果遇到即将加入并查集的边的两端节点 本来就在并查集了,说明构成了环,此时表示删除当前边后不是有向树;若最终没有构成环,则表示删除当前边后是有向树。
2️⃣不存在节点的入度为2。如下图:
此时形成环。对于并查集就是存在某个边的两端已经存在于同一集合中了。
对于并查集:将所有边的两端节点分别加入并查集,如果遇到即将加入并查集的边的两端节点 本来就在并查集了,说明构成了环。返回当前边。
java
import java.util.*;
public class Main {
static int[] father;
public static void main (String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
father = new int[n+1];
int[][] edges = new int[n][2];
int[] inDegree = new int[n+1];
for (int i=0;i<n;i++) {
int u = sc.nextInt(); // father
int v = sc.nextInt(); // child
edges[i][0] = u;
edges[i][1] = v;
inDegree[v]++;
}
List<Integer> list = new ArrayList<>();
for (int i=edges.length-1;i>=0;i--) {
if (inDegree[edges[i][1]] == 2) {
list.add(i); // 收集入度为2的边的索引 依题目是两条边 list.size() == 2
}
}
if (list.size() > 0) {
if (isTreeAfterRemoveEdge(list.get(0),edges)) {
System.out.println(edges[list.get(0)][0] + " " + edges[list.get(0)][1]);
return;
} else {
System.out.println(edges[list.get(1)][0] + " " + edges[list.get(1)][1]);
return;
}
}
isCir(edges);
}
public static boolean isTreeAfterRemoveEdge(int index,int[][] edges) {
init();
for (int i=0;i<edges.length;i++) {
if (i == index) continue;
if (isSame(edges[i][0],edges[i][1])) {
return false;
} else {
join(edges[i][0],edges[i][1]);
}
}
return true;
}
public static void isCir(int[][] edges) {
init();
for (int i=0;i<edges.length;i++) {
if (isSame(edges[i][0],edges[i][1])) {
System.out.println(edges[i][0] + " " + edges[i][1]);
return;
} else {
join(edges[i][0],edges[i][1]);
}
}
}
public static void init() {
for (int i=1;i<father.length;i++) {
father[i]=i;
}
}
public static int find(int u) {
if (u == father[u]) return u;
return father[u] = find(father[u]);
}
public static void join(int u,int v) {
u = find(u);
v = find(v);
if (u == v) return;
father[v] = u;
}
public static boolean isSame(int u,int v) {
u = find(u);
v = find(v);
return u == v;
}
}