在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n
个节点(节点值不重复,从 1
到 n
)的树及一条附加的有向边构成。附加的边包含在 1
到 n
中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges
。 每个元素是一对 [ui, vi]
,用以表示 有向 图中连接顶点 ui
和顶点 vi
的边,其中 ui
是 vi
的一个父节点。
返回一条能删除的边,使得剩下的图是有 n
个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
示例 1:
输入:edges = [[1,2],[1,3],[2,3]] 输出:[2,3]
示例 2:
输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]] 输出:[4,1]
思路:冗余边一定从冲突节点的两个父节点中产生
冗余连接II是在冗余连接的基础上,让树变成了有向图。要求是,一共有 n 个节点,正常来说一个 n 个节点的树,应该有 n-1 条边(保证了除了根节点自己没有父节点外,其他所有的节点应该有且只有唯一的父节点),但是现在有 n 条边,意味着有一条边冗余,我们需要找出这条冗余边。
在多了一条附加的边之后,可能有以下两种情况:
- 附加的边指向根节点,则包括根节点在内的每个节点都有一个父节点,此时图中一定有环路。
- 附加的边指向非根节点,则恰好有一个节点(即被附加的边指向的节点)有两个父节点,此时图中可能有环路也可能没有环路。
在遍历每条边的过程中,通过并查集维护导致冲突的边和导致环路出现的边,由于只有一条附加的边,因此最多有一条导致冲突的边和一条导致环路出现的边。
当访问到边 [u,v] 时,进行如下操作:
- 如果此时已经有
,说明 v 有两个父节点,当前的边 [u, v] 记为导致冲突的边。
- 否则,令
,然后在并查集中分别找出u、v的祖先,如果祖先相同,说明这条边导致环路出现,将当前的边 [u,v] 记为导致环路出现的边,如果祖先不同则在并查集中将 u 和 v 合并。
根据上述操作,同一条边不可能同时被记为导致冲突的边和导致环路出现的边。如果访问到的边确实同时导致冲突和环路出现,则这条边被记为导致冲突的边。
在遍历图中所有的边后,根据是否存在导致冲突的边和导致环路出现的边,得到附加的边。
如果没有导致冲突的边,则说明附加的边一定导致环路出现,而且是在环路中的最后一条被访问到的边,因此附加的边即为导致环路出现的边。(没有冲突则说明一定有环,且导致环的边一定是由正确的根节点指出的)
如果有导致冲突的边,记这条边为 [u,v] ,则有两条边指向 v ,另一条边为 [parent[v],v],需要通过判断是否有导致环路的边决定哪条边是附加的边。(冗余边一定从冲突节点的两个父节点中产生)
- 如果有导致环路的边,则附加的边不可能是 [u,v](因为 [u,v] 已经被记为导致冲突的边,不可能被记为导致环路出现的边),因此附加的边是 [parent[v],v]。
- 如果没有导致环路的边,则附加的边是后被访问到的指向 v 的边,因此附加的边是 [u,v]。
代码(C++)
class UF {
private:
int _count;
vector<int> parent;
public:
UF(int n) : _count(n), parent(n+1) {
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
bool union_(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
parent[rootQ] = rootP;
_count--;
return true;
}
int count() {
return _count;
}
};
class Solution {
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int n = edges.size();
UF uf(n);
vector<int> parent(n+1); // 这里的parent只记录原始的 a->b ,没有也不需要经过路径压缩
int conflict = -1, cycle = -1;
for (int i = 0; i < n; ++i) {
int u = edges[i][0];
int v = edges[i][1];
if (parent[v] != 0) {
conflict = i;
} else {
parent[v] = u;
if (uf.find(u) == uf.find(v)) {
cycle = i;
} else {
uf.union_(u, v);
}
}
}
if (conflict < 0) {
return {edges[cycle][0], edges[cycle][1]};
} else {
vector<int> conflictEdge = edges[conflict];
if (cycle >= 0) {
return {parent[conflictEdge[1]], conflictEdge[1]};
} else {
return {conflictEdge[0], conflictEdge[1]};
}
}
}
};
代码(Python)
class UF:
def __init__(self, n):
self._count = n
self.parent = list(range(n+1))
def union(self, p, q):
rootP = self.find(p)
rootQ = self.find(q)
self.parent[rootQ] = rootP
self._count -= 1
return True
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def count(self):
return self._count
class Solution:
def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
n = len(edges)
uf = UF(n)
parent = list(range(n+1))
conflict = -1
cycle = -1
for i, (u, v) in enumerate(edges):
if parent[v] != v:
conflict = i
else:
parent[v] = u
if uf.find(u) == uf.find(v):
cycle = i
else:
uf.union(u, v)
if conflict < 0:
return [edges[cycle][0], edges[cycle][1]]
else:
conflictEdge = edges[conflict]
if cycle >= 0:
return [parent[conflictEdge[1]], conflictEdge[1]]
else:
return [conflictEdge[0], conflictEdge[1]]