在本问题中,有根树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。每一个节点只有一个父节点,除了根节点没有父节点。
输入一个有向图,该图由一个有着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;
}
}
}
在区分三种情况的时候, 我们可以结合图来分析,这样思路会更加清晰。