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

一、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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值