【leetcode】图论刷题总结

深度优先搜索

深度优先搜索(dfs)是可一个方向去搜,直到遇到绝境了,搜不下去了,再换方向(换方向的过程就涉及到了回溯),所以用递归的方式来实现是最方便的

dfs实现三部曲

  • 确认递归函数,参数。一般情况,深搜需要二维数组数组结构保存所有路径,需要一维数组保存单一路径,这种保存结果的数组,我们可以定义一个全局变量,避免让递归函数参数过多
  • 确认中止条件。遇到终止条件不仅是结束本层递归,同时也是我们收获结果的时候
  • 处理目前搜索节点出发的路径。一般这里就是一个for循环的操作,去遍历 目前搜索节点 所能到的所有节点

LeetCode200题 岛屿数量

计算连通块的数量

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        res = 0
        self.grid = grid
        for i in range(len(self.grid)):
            for j in range(len(self.grid[0])):
                if self.grid[i][j] == '1':
                    res += 1
                    # dfs作用是将和i,j连在一起的陆地置0
                    self.dfs(i, j)
        return res
    
    def dfs(self, x, y):
        for i, j in [[0, -1], [0, 1], [1, 0], [-1, 0]]:
            if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]) and self.grid[x + i][y + j] == '1':
                self.grid[x + i][y + j] = '0'
                self.dfs(x + i, y + j)

LeetCode695题 岛屿的最大面积

计算最大的连通块

class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        res = 0
        self.grid = grid
        for i in range(len(self.grid)):
            for j in range(len(self.grid[0])):
                if self.grid[i][j] == 1:
                    self.count = 0
                    self.dfs(i, j)
                    res = max(res, self.count)
        return res

    
    def dfs(self, x, y):
        self.count += 1
        self.grid[x][y] = 0
        for i, j in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
            if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]) and self.grid[x + i][y + j] == 1:
                self.grid[x + i][y + j] = 0
                self.dfs(x + i, y + j)

LeetCode417题 太平洋大西洋水流问题

从太平洋边上的节点 逆流而上,将遍历过的节点都标记上。 从大西洋的边上节点 逆流而长,将遍历过的节点也标记上。 然后两方都标记过的节点就是既可以流太平洋也可以流大西洋的节点

class Solution:
    def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
        m, n = len(heights), len(heights[0])
        is_pac = [[False] * n for _ in range(m)]
        is_atl = [[False] * n for _ in range(m)]
        # 以边界元素为种子,分别进行深度优先搜索
        for i in range(n):
            self.dfs(0, i, is_pac, heights)
            self.dfs(m - 1, i, is_atl, heights)
        for i in range(m):
            self.dfs(i, 0, is_pac, heights)
            self.dfs(i, n - 1, is_atl, heights)
        # 记录结果
        res = []
        for i in range(m):
            for j in range(n):
                if is_pac[i][j] and is_atl[i][j]:
                    res.append([i, j])
        return res

    def dfs(self, x, y, path, heights):
        path[x][y] = True
        for i, j in [[0, 1], [1, 0], [-1, 0], [0, -1]]:
            if 0 <= x + i < len(heights) and 0 <= y + j < len(heights[0]):
                if heights[x + i][y + j] >= heights[x][y] and not path[x + i][y + j]:
                    self.dfs(x + i, y + j, path, heights)

LeetCode827题 最大人工岛

第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录

第二步:再一次遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积

class Solution:
    def largestIsland(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        self.pos_2_island = dict() # 每个位置对应的岛屿id
        island_num = -1 # 统计岛屿数量
        island_area = [] # 存储岛屿面积
        # 计算每个岛屿面积,记录每个位置对应的岛屿id
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    island_num += 1
                    r = self.dfs(i, j, grid, island_num)
                    island_area.append(r)
        res = max(island_area) if island_num >= 0 else 0
        # 找到0的位置,然后计算0能串联起来的岛屿最大面积
        for x in range(m):
            for y in range(n):
                if grid[x][y] == 0:
                    s = 1
                    visited_id = []
                    for i, j in [[0, 1], [1, 0], [0, -1], [-1, 0]]:
                        if 0 <= x + i < m and 0 <= y + j < n and grid[x + i][y + j] != 0:
                            island_id = self.pos_2_island[(x + i, y + j)]
                            if island_id not in visited_id:
                                s += island_area[island_id]
                                visited_id.append(island_id)
                    res = max(res, s)
        return res

    def dfs(self, x, y, grid, island_num):
        res = 1
        grid[x][y] = -1
        self.pos_2_island[(x, y)] = island_num
        for i, j in [[0, 1], [1, 0], [0, -1], [-1, 0]]:
            if 0 <= x + i < len(grid) and 0 <= y + j < len(grid[0]) and grid[x + i][y + j] == 1:
                res += self.dfs(x + i, y + j, grid, island_num)
        return res

LeetCode547题 省份数量

计算连通块的数量:遍历所有城市,对于每个城市,如果该城市尚未被访问过,则从该城市开始深度优先搜索,通过矩阵isConnected得到与该城市直接相连的城市有哪些,这些城市和该城市属于同一个连通分量,然后对这些城市继续深度优先搜索,直到同一个连通分量的所有城市都被访问到,即可得到一个省份。遍历完全部城市以后,即可得到连通分量的总数,即省份的总数

class Solution:
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        res = 0
        self.visited = set()
        for i in range(len(isConnected)):
            if i not in self.visited:
                res += 1
                self.visited.add(i)
                self.dfs(i, isConnected)
        return res
        
    def dfs(self, i, isConnected):
        for j in range(len(isConnected)):
            if j not in self.visited and isConnected[i][j] == 1:
                self.visited.add(j)
                self.dfs(j, isConnected)

LeetCode797题 所有可能的路径

找有向图两个节点间的所有路径

class Solution:
    def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
        self.res = []
        self.dfs(graph, 0, [0])
        return self.res
    
    def dfs(self, graph, node, path):
        # node代表当前路径最后一个元素
        if node == len(graph) - 1:
            self.res.append(path)
        for i in graph[node]:
            self.dfs(graph, i, path + [i])

LeetCode207题 课程表

判断有向图是否有环:使用三色标记法,DFS过程中 记录遍历节点的3种状态

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 构造邻接矩阵
        g = [[] for _ in range(numCourses)]
        for a, b in prerequisites:
            g[b].append(a)
        # dfs
        # visited需要记录3个状态,0代表未访问,1代表正在访问,2代表访问完毕
        visited = [0] * numCourses
        for i in range(numCourses):
            if self.dfs(i, g, visited):
                return False
        return True
    
    def dfs(self, i, g, visited):
        # dfs函数返回是否有环
        if visited[i] == 2:
            return False
        visited[i] = 1
        for j in g[i]:
            if visited[j] == 1 or self.dfs(j, g, visited):
                return True
        visited[i] = 2
        return False

广度优先搜索

广度优先搜索(bfs)是先把本节点所连接的所有节点遍历一遍,走到下一个节点的时候,再把连接节点的所有节点遍历一遍,搜索方向更像是四面八方的搜索过程

广搜的搜索方式就适合于解决两个点之间的最短路径问题。因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路

bfs代码模板

保存遍历元素的容器,用队列、用栈都是可以的,但习惯用队列(可以保证每层转圈方向统一)

from collections import deque

dir = [(0, 1), (1, 0), (-1, 0), (0, -1)]  # 创建方向元素


def bfs(grid, visited, x, y):
    queue = deque()  # 初始化队列
    queue.append((x, y))  # 放入第一个元素/起点
    visited[x][y] = True  # 标记为访问过的节点
    while queue:  # 遍历队列里的元素
        curx, cury = queue.popleft()  # 取出第一个元素
        for dx, dy in dir:  # 遍历四个方向
            nextx, nexty = curx + dx, cury + dy
            if nextx < 0 or nextx >= len(grid) or nexty < 0 or nexty >= len(grid[0]):  # 越界了,直接跳过
                continue
            if not visited[nextx][nexty]:  # 如果节点没被访问过  
                queue.append((nextx, nexty))  # 加入队列
                visited[nextx][nexty] = True  # 标记为访问过的节点

LeetCode200题 岛屿数量

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        res = 0
        self.grid = grid
        for i in range(len(self.grid)):
            for j in range(len(self.grid[0])):
                if self.grid[i][j] == '1':
                    res += 1
                    self.bfs(i, j)
        return res


    def bfs(self, x, y):
        from collections import deque
        queue = deque()
        queue.append([x, y])
        while queue:
            x, y = queue.popleft()
            for i, j in [[0, -1], [0, 1], [1, 0], [-1, 0]]:
                if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]) and self.grid[x + i][y + j] == '1':
                    self.grid[x + i][y + j] = '0'
                    queue.append([x + i, y + j])

LeetCode127题 单词接龙

求起点和终点的最短路径长度,这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        wordSet = set(wordList)
        if len(wordSet)== 0 or endWord not in wordSet:
            return 0
        # 记录到每个单词的最小路径
        mapping = {beginWord: 1}
        queue = deque([beginWord])
        while queue:
            word = queue.popleft()
            path = mapping[word]
            for i in range(len(word)):
                word_list = list(word)
                # 以26个字符作为广搜的对象
                for j in range(26):
                    word_list[i] = chr(ord('a') + j)
                    newWord = ''.join(word_list)
                    if newWord == endWord:
                        return path + 1
                    if newWord in wordSet and newWord not in mapping:
                        mapping[newWord] = path + 1
                        queue.append(newWord)                      
        return 0

 LeetCode841题 钥匙和房间

class Solution:
    def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
        visited = set()
        from collections import deque
        queue = deque([0])
        while queue:
            index = queue.popleft()
            visited.add(index)
            if len(visited) == len(rooms):
                return True
            for i in rooms[index]:
                if i not in visited:
                    queue.append(i)
        return False

LeetCode815题 公交路线

记录经过每个车站的公交车编号,通过公交车编号找到邻居车站

class Solution:
    def numBusesToDestination(self, routes: List[List[int]], source: int, target: int) -> int:
        if source == target:
            return 0
        # 记录经过每个车站的公交车编号,通过公交车编号找到邻居车站
        stop_2_bus = dict()
        for i in range(len(routes)):
            for j in routes[i]:
                if j not in stop_2_bus:
                    stop_2_bus[j] = [i]
                else:
                    stop_2_bus[j].append(i)
    
        if source not in stop_2_bus or target not in stop_2_bus:
            return -1
        
        # BFS
        dis = {source: 0} # 记录source到各车站最少的公交车数量
        from collections import deque
        queue = deque([source])
        while queue:
            s = queue.popleft()
            for i in stop_2_bus[s]:
                if routes[i]:
                    for j in routes[i]:
                        if j not in dis:
                            dis[j] = dis[s] + 1
                            queue.append(j)
                    routes[i] = None
        return dis.get(target, -1)

二分图染色

LeetCode785题 判断二分图

  • 颜色标记:使用数组 color 记录每个节点的颜色(0 或 1),初始为未染色(-1)
  • 遍历连通分量:对每个未染色的节点启动 BFS/DFS,确保处理所有连通分量
  • 冲突检测:在遍历过程中,若相邻节点颜色相同,则存在奇数环,图不是二分图

时间复杂度为O(节点数 + 边数)

class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n = len(graph)
        color = [-1] * n  # 初始化颜色数组,-1表示未染色
        for i in range(n):
            if color[i] == -1:  # 当前节点未染色,开始处理该连通分量
                queue = deque([i])
                color[i] = 0  # 初始染色为0
                while queue:
                    node = queue.popleft()
                    for neighbor in graph[node]:
                        if color[neighbor] == -1:  # 邻接节点未染色
                            color[neighbor] = color[node] ^ 1  # 染相反颜色
                            queue.append(neighbor)
                        elif color[neighbor] == color[node]:  # 颜色冲突
                            return False
        return True

拓扑排序

给出一个有向无环图,把这个有向图转成线性的排序就叫拓扑排序

算法步骤:每次选入度为0的点,然后删除这个点和它的出边,拓扑排序可能有多种结果

拓扑排序还可以用来判断是否有环

LeetCode210题 课程表II

用BFS实现拓扑排序,队列存放入度为0的节点

class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        res = []
        # 存储有向图
        g = [[] for _ in range(numCourses)]
        # 存储每个节点的入度
        in_dgree = [0] * numCourses
        for u, v in prerequisites:
            g[v].append(u)
            in_dgree[u] += 1
        # BFS
        # 将所有入度为0的节点放入队列中
        queue = collections.deque([i for i in range(numCourses) if in_dgree[i] == 0])
        while queue:
            u = queue.popleft()
            for v in g[u]:
                in_dgree[v] -= 1
                # 如果相邻节点v的入度为0,就可以选v对应的课程了
                if in_dgree[v] == 0:
                    queue.append(v)
            res.append(u)
        if len(res) != numCourses:
            res = []
        return res

Dijkstra算法-单源最短路径算法

算法原理

定义 g[i][j] 表示节点 i 到节点 j 这条边的边权。如果没有 i 到 j 的边,则 g[i][j]=∞

定义 dis[i] 表示起点 k 到节点 i 的最短路径长度,一开始 dis[k]=0,其余 dis[i]=∞ 表示尚未计算出,我们的目标是计算出最终的 dis 数组

Dijkstra算法流程:

  • 首先更新起点 k 到其邻居 y 的最短路,即更新 dis[y] = g[k][y]
  • 然后取除了起点 k 以外的 dis[i] 的最小值,假设最小值对应的节点是 3。此时可以断言:dis[3] 已经是 k 到 3 的最短路长度,不可能有其它 k 到 3 的路径更短!反证法:假设存在更短的路径,那我们一定会从 k 出发经过一个点 u,它的 dis[u] 比 dis[3] 还要小,然后再经过一些边到达 3,得到更小的 dis[3]。但 dis[3] 已经是最小的了,并且图中没有负数边权,所以 u 是不存在的,矛盾。故原命题成立,此时我们得到了 dis[3] 的最终值
  • 用节点 3 到其邻居 y 的边权 g[3][y] 更新 dis[y]:如果 dis[3]+g[3][y]<dis[y],那么更新 dis[y] 为 dis[3]+g[3][y],否则不更新
  • 然后取除了节点 k,3 以外的 dis[i] 的最小值,重复上述过程,当所有点的最短路都已确定时,算法结束

代码写法一:朴素Dijkstra(适用于稠密图)

时间复杂度O(n^2)、空间复杂度O(n^2),n为节点数

代码写法二:堆优化Dijkstra(适用于稀疏图)

时间复杂度O(m*logm)、空间复杂度O(m),m为边数

寻找最小值的过程可以用一个最小堆来快速完成:

  • 一开始把 (dis[k],k) 二元组入堆
  • 当节点 x 首次出堆时,dis[x] 就是写法一中寻找的最小最短路(注意,如果一个节点 x 在出堆前,其最短路长度 dis[x] 被多次更新,那么堆中会有多个重复的 x,并且包含 x 的二元组中的 dis[x] 是互不相同的,我们可以用出堆的最短路值(记作 dx)与当前的 dis[x] 比较,如果 dx>dis[x] 说明 x 之前出堆过,我们已经更新了 x 的邻居的最短路,所以这次就不用更新了,继续外层循环)
  • 更新邻居节点 dis[y] ,把 (dis[y],y) 二元组入堆

LeetCode743题 网络延迟时间

朴素写法

class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        # 构造邻接矩阵
        g = [[float('inf')] * n for _ in range(n)]
        for u, v, w in times:
            g[u - 1][v - 1] = w
        # Dijkstra算法
        res = 0
        dis = [float('inf')] * n
        dis[k - 1] = 0
        done = [False] * n
        while True:
            x = -1
            # 找未确定的节点中,dis最小节点x,此时dis[x]就是k到x的最短路径
            for i in range(n):
                if not done[i] and (x < 0 or dis[i] < dis[x]):
                    x = i
            # 当每个节点都已更新完毕时,返回结果
            if x < 0:
                return res
            # 当有节点无法到达时,返回-1
            if dis[x] == float('inf'):
                return -1
            # 更新结果
            res = dis[x]
            done[x] = True
            # 更新x的邻居节点距离
            for j in range(n):
                dis[j] = min(dis[j], dis[x] + g[x][j])

堆优化写法 

from heapq import heappush, heappop
class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        # 构造邻接表
        g = [[] for _ in range(n)]
        for u, v, w in times:
            g[u - 1].append((v - 1, w))
        # Dijkstra算法
        dis = [float('inf')] * n
        dis[k - 1] = 0
        heap = [(0, k - 1)]
        while heap:
            dx, x = heappop(heap)
            if dx > dis[x]:  # x之前出堆过
                continue
            for y, d in g[x]:
                new_dis = dx + d
                if new_dis < dis[y]:
                    dis[y] = new_dis  # 更新x的邻居的最短路
                    heappush(heap, (new_dis, y))
        mx = max(dis)
        return mx if mx < float('inf') else -1

LeetCode1514题 概率最大的路径

无向图&稀疏图,两点之间概率最大路径 

class Solution:
    def maxProbability(self, n: int, edges: List[List[int]], succProb: List[float], start_node: int, end_node: int) -> float:
        # 稀疏图邻接矩阵
        g = [[] for _ in range(n)]
        for i in range(len(edges)):
            g[edges[i][0]].append((edges[i][1], succProb[i]))
            g[edges[i][1]].append((edges[i][0], succProb[i]))
        # Dijkstra算法,由于是稀疏图,用堆优化版本更优
        from heapq import heappush, heappop
        dis = [float('-inf')] * n
        dis[start_node] = 1
        heap = [(-1, start_node)]
        while heap:
            d, x = heappop(heap)
            # 若堆顶节点之前弹出过,直接跳过
            if -d < dis[x]:
                continue
            if x == end_node:
                return -d
            for v, p in g[x]:
                new_dis = -d * p
                if new_dis > dis[v]:
                    dis[v] = new_dis
                    heappush(heap, (-new_dis, v))
        return 0

Floyd算法-全源最短路径

算法原理

Floyd算法是一种用于求解加权图中所有顶点对之间最短路径的动态规划算法。其核心思想是通过逐步扩展允许的中间节点集合,迭代更新最短路径信息

时间复杂度O(n^3),适用于中小规模图(n ≤ 500)
空间复杂度O(n^2),需存储距离矩阵

LeetCode1334题 阈值距离内邻居最少的城市

class Solution:
    def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int:
        # dp[k][i][j]代表i-j的最短路径,并且这条最短路的中间节点编号都<=k
        # dp[k][i][j] = min(dp[k - 1][i][j], dp[k - 1][i][k] + dp[k - 1][k][j]) 分别代表不选k / 选k
        # 可压缩为2维
        dp = [[float('inf')] * n for _ in range(n)]
        for f, t, w in edges:
            dp[f][t], dp[t][f] = w, w
        for i in range(n):
            dp[i][i] = 0
        for k in range(n):
            dp_tmp = dp[:]
            for i in range(n):
                for j in range(i, n):
                    dp_tmp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j])
                    dp_tmp[j][i] = dp[i][j]
            dp = dp_tmp
        min_num = n
        res = 0
        for i in range(n):
            r = sum([1 for j in dp[i] if j <= distanceThreshold]) - 1
            if r <= min_num:
                res = i
                min_num = r
        return res

最小生成树

定义

最小生成树是连通无向图中满足以下条件的子图:

  • 包含所有顶点:覆盖原图所有节点
  • 树结构:无环路且连通(边数 = 顶点数 - 1)
  • 权值最小:所有边的权重之和最小

算法原理

算法时间复杂度适用场景核心思想
PrimO(E+Vlog⁡V)稠密图(边多)

贪心选节点,逐步扩展最小边

  • 任选起始顶点加入 MST 集合

  • 选择连接 MST 集合与非 MST 集合的最小权重边

  • 将边的另一端点加入 MST 集合

  • 重复直到包含所有顶点

KruskalO(Elog⁡E)稀疏图(边少)

贪心选边,按边权升序选择,避免环路

  • 将所有边按权重升序排序

  • 初始化空集合存放 MST 边

  • 依次选择最小边,若加入后不形成环,则保留

  • 重复直到选择 V−1 条边

并查集

并查集(Union-find Sets)常用来解决连通性问题。当我们需要判断两个元素是否在同一个集合里的时候,就要想到用并查集。并查集主要有两个功能:

  • 将两个元素添加到一个集合中
  • 判断两个元素在不在同一个集合

并查集的基本操作:

  • 初始化
  • 合并(union,将两个节点连在同一个根节点上)
  • 查询(find,判断这个节点的祖先节点是哪个)

所以判断两个节点是否在同一个集合(是否连通),就是判断两个节点是不是同一个根节点

路径压缩后的并查集时间复杂度在O(logn)与O(1)之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1),在第一次查询的时候,相当于是n叉树上从叶子节点到根节点的查询过程,时间复杂度是logn,但路径压缩后,后面的查询操作都是O(1)

LeetCode1971题 寻找图中是否存在路径

class Solution:
    def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
        # 初始化
        father = [i for i in range(n)]
        # find
        def find(i):
            if father[i] == i:
                return i
            # 路径压缩操作,否则会超时
            father[i] = find(father[i])
            return father[i]
        # union
        for u, v in edges:
            father[find(u)] = find(v)
        return find(source) == find(destination)

 LeetCode1202题 交换字符串中的元素

class Solution:
    def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str:

        def find(i):
            if father[i] == i:
                return father[i]
            father[i] = find(father[i])
            return father[i]

        father = [i for i in range(len(s))]
        for u, v in pairs:
            # union
            father[find(u)] = find(v)
        connected_components = collections.defaultdict(list)
        for i in range(len(s)):
            # find
            father[i] = find(i)
            connected_components[father[i]].append(i)
        res = [''] * len(s)
        # 按连通图内的字符排序 重新赋值
        for nodes in connected_components.values():
            sort_char = sorted(s[i] for i in nodes)
            for i, char in zip(nodes, sort_char):
                res[i] = char

        return ''.join(res)

LeetCode684题 冗余连接

如果一棵树有 n 个节点,则这棵树有 n−1 条边。树是一个连通且无环的无向图,在树中多了一条附加的边之后就会出现环,因此附加的边即为导致环出现的边

可以通过并查集寻找附加的边。初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量

  • 如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量
  • 如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为附加的边,将当前的边作为答案返回

最差时间复杂度为O(nlogn),其中 n 是图中的节点个数。需要遍历图中的 n 条边,对于每条边,需要对两个节点查找祖先,如果两个节点的祖先不同则需要进行合并,需要进行 2 次查找和最多 1 次合并

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        res = []
        father = [i for i in range(len(edges))]
        def find(i):
            if i == father[i]:
                return i
            father[i] = find(father[i])
            return father[i]
        for i, j in edges:
            a = find(i - 1)
            b = find(j - 1)
            if a == b:
                res = [i, j]
            father[a] = b
        return res

LeetCode685题 冗余连接II

有2种情况:

  • 附加的边指向根节点,则会出现有向环
  • 附加的边指向非根节点,则有节点的入度为2,不一定出现环
class Solution:
    def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
        # 有2种情况:
        # 附加的边指向根节点,则会出现有向环
        # 附加的边指向非根节点,则有节点的入度为2,不一定出现环

        def find(i):
            if i == ancestor[i]:
                return i
            ancestor[i] = find(ancestor[i])
            return ancestor[i]
        
        cycle = conflict = -1
        ancestor = [i for i in range(len(edges))]
        father = [i for i in range(len(edges))]
        for i, (u, v) in enumerate(edges):
            # 说明v的入度为2,有导致冲突的边
            if father[v - 1] != v - 1:
                conflict = i
            else:
                a = find(u - 1)
                b = find(v - 1)
                # 说明有导致出现环的边
                if a == b:
                    cycle = i
                ancestor[b] = a
                father[v - 1] = u - 1
        if conflict > 0 and cycle < 0:
            return edges[conflict]
        elif conflict < 0 and cycle > 0:
            return edges[cycle]
        else:
            # 此时按照edges的顺序,肯定是先出现环,再出现冲突;否则只会出现冲突
            # 此时导致冲突的边不是edges[conflict],因为没他也会导致出现环
            # 而是edges[conflict]的孩子节点与更早的父亲节点 形成的边
            conflict_chlid = edges[conflict][1] - 1
            return [father[conflict_chlid] + 1, conflict_chlid + 1]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值