一、108. 冗余连接
题目连接:108. 冗余连接 (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——108. 冗余连接
思路:从前向后遍历每一条边,如果边上的点不在同一个集合,就加入集合(即,相连);如果两个节点在同一个集合,再加入变就会构成一个环,这条边就要删掉。
class UnionFindSet:
def __init__(self, n):
# 节点索引从1开始,所以到 n + 1
self.parent = list(range(n + 1))
def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
def isSame(self, u, v):
u = self.find(u)
v = self.find(v)
return u == v
def join(self, u, v):
root_u = self.find(u)
root_v = self.find(v)
if root_u != root_v:
self.parent[root_v] = root_u
if __name__ == '__main__':
n = int(input()) # 获取输入节点数量
ufs = UnionFindSet(1001) # 设置上限,题目要求1000
for _ in range(n):
s, t = map(int, input().split()) # 输入连接的边
# 检查 s, t 是否在一个集合里,如果在同一集合,则这条线为冗余,直接输出结果
if ufs.isSame(s, t):
print(s, t)
break
# 否则将 s, t 相连
else:
ufs.join(s, t)
二、109. 冗余连接 II
题目连接:109. 冗余连接II (kamacoder.com)
文章讲解:代码随想录 (programmercarl.com)——109. 冗余连接 II
题意:本题为有向图,是由一颗有向树 + 一条边组成,找到这条边并删掉,恢复为有向树。
思路:有向树,只有根节点入度为0,其余节点入度为1
case1. 有入度为2的点,删除哪一条边都可以,此时选择删除最后出现的一条边;
case2. 有入度为2的点,只能删除特定的一条边,判断删除哪一条边可以构成有向树;
case3. 没有入度为2的点,此时构成有向环了,删掉构成环的边。
Note:一定要从后向前遍历,因为如果两条边删哪一条都可以,要删最后的那一条。
# 定义并查集类
class UnionFindSet:
def __init__(self, n):
self.parent = list(range(n + 1))
def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
def isSame(self, u, v):
return self.find(u) == self.find(v)
def join(self, u, v):
root_u = self.find(u)
root_v = self.find(v)
if root_u != root_v:
self.parent[root_v] = root_u
# 找出在有向图中需要删除的边,以确保图中没有环
def getRemoveEdge(edges, n):
ufs = UnionFindSet(n)
# 遍历所有的边
for edge in edges:
# 如果在同一个集合内,会构成有向环,删掉对应的边
if ufs.isSame(edge[0], edge[1]):
print(edge[0], edge[1])
return
# 否则合并到一个集合
else:
ufs.join(edge[0], edge[1])
# 检查在删除特定边后是不是有向树
def isTreeAfterRemove(edges, delete_edge, n):
ufs = UnionFindSet(n)
# 遍历所有的边
for i, edge in enumerate(edges):
# 跳过要删除的边
if i == delete_edge:
continue
# 如果构成有向环,一定不是树
if ufs.isSame(edge[0], edge[1]):
return False
# 将两个点加入到集合中
ufs.join(edge[0], edge[1])
return True
if __name__ == '__main__':
n = int(input())
edges = [] # 创建一个边的列表
inDegree = [0] * (n + 1) # 记录节点入度
# 遍历输入的边
for _ in range(n):
# 读取边的起点 s 和终点 t
s, t = map(int, input().split())
# 记录 t 的入度
inDegree[t] += 1
# 把边记录到 edges 中
edges.append([s, t])
vec = [] # 记录入度为2的点对应的边
# 找入度为2的节点对应的边,倒叙查找
for i in range(n - 1, -1, -1):
# 如果入度为 2,将其添加到入度为2的点的集合
if inDegree[edges[i][1]] == 2:
vec.append(i)
# case1 和 case2
if vec:
# case1. 删除最后一条入度为2的边后是否为树,如果仍为数,打印这条边
if isTreeAfterRemove(edges, vec[0], n):
print(edges[vec[0]][0], edges[vec[0]][1])
else:
# case2. 删除第一条边后不是树,检查是否有,检查是否有第二条边
if len(vec) > 1:
print(edges[vec[1]][0], edges[vec[1]][1])
exit(0)
# case3. 如果没有入度为2的点,一定有有向环,删掉有向环的边
getRemoveEdge(edges, n)