无向图找环
- DFS + parent node
- union find
- topological sort(无向图改有向图)❌
DFS + parent node
因为是双向的edge,所以用parent node保持单向的dfs遍历
对于当前节点cur的每一个neighbour邻居节点:
- 没被visted过,看
dfs(neighbour)是否为true,是的话dfs(cur)也return true - 被visted过,需要check是一个真的cycle还是说只是回头路。不可以走回头路,只能单向行进
- if
visited[neighbour] and neighbour != parent, then cycle found!
- if
def isCyclicUndirectedGraph(graph: List[List[int]], n: int) -> bool:
visited = [False] * n
def isCyclicUntil(cur, parent):
visited[cur] = True
for neighbour in graph[cur]:
if not visited[neighbour]:
if isCyclicUntil(neighbour, cur):
return True
elif parent != neighbour: # cycle detected
return True
return False
for i in range(n):
if not visited[i]:
if isCyclicUntil(i, -1):
return True
return False
union find
- 拓扑排序
- 先对edge做去重处理,把双向的两个edge变成只剩下一个
- 遍历所有edge,如果存在一条edge的两端已经connected,就是环
def isCyclicUndirectedGraph(graph: List[List[int]], n: int) -> bool:
uf = UF(n)
edges = set()
for i in range(n):
for nei in graph[i]:
edge = tuple(sorted([i, nei]))
edges.add(edge)
for x, y in edges:
if uf.find(x) == uf.find(y): # if x and y are already in the same component
return True
uf.union(x, y)
return False
class UF:
def __init__(self, n):
self.parent = [i for i in range(n)]
def find(self, x) -> int:
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y) -> None:
self.parent[self.find(x)] = self.find(y)
261. 以图判树(Medium)
Solu 1:DFS + parent nod
照抄dfs + parent node模版
Code 1:
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
def buildGraph():
graph = {i: [] for i in range(n)}
for x, y in edges:
graph[x].append(y)
graph[y].append(x)
return graph
def dfs(cur, prev):
visited[cur] = True
for nei in graph[cur]:
if not visited[nei]:
if dfs(nei, cur):
return True
elif nei != prev: # cycle detected
return True
return False
if len(edges) != n - 1:
return False
graph = buildGraph()
visited = [False] * n
if dfs(0, -1): # start from 0 (the only root)
return False
return all(visited) # tree只能有一个component
Solu 2:union find
照抄union-find模版
Code 2:
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
if len(edges) != n - 1:
return False
uf = UF(n)
for x, y in edges:
if uf.find(x) == uf.find(y):
return False
uf.union(x, y)
return True
class UF:
def __init__(self, n):
self.parent = [i for i in range(n)]
def find(self, x) -> int:
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y) -> None:
self.parent[self.find(x)] = self.find(y)
有向图找环
拓扑排序
- 有环的图是无法做“拓扑排序”的
- “拓扑排序”通常用来排序具有依赖关系的任务
- 一个DAG的“拓扑排序”可能有多个
class Solution:
def isCyclicDirectedGraph(self, graph: List[List[int]], n: int) -> bool:
inDeg = [0] * n
for i in range(n):
for j in graph[i]:
inDeg[j] += 1
queue = [i for i in range(n) if inDeg[i] == 0]
count = 0
while queue:
node = queue.pop(0)
count += 1
for nei in graph[node]:
inDeg[nei] -= 1
if inDeg[nei] == 0:
queue.append(nei)
return count == n
210. 课程表 II(Medium)
Solu:拓扑排序
照抄拓扑排序模版
Code:
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
indeg = [0] * numCourses
graph = [[] for _ in range(numCourses)]
for course, prerequisite in prerequisites:
indeg[course] += 1
graph[prerequisite].append(course)
queue = [i for i in range(numCourses) if indeg[i] == 0]
res = []
while queue:
course = queue.pop(0)
res.append(course)
for neighbor in graph[course]:
indeg[neighbor] -= 1
if indeg[neighbor] == 0:
queue.append(neighbor)
return res if len(res) == numCourses else []
DFS + 双array标记
visited:如果一个node已经被visited过了(被check过了),就没必要再DFS一次了- 即:代表是否已被其他节点启动的DFS访问
recStack:当前recursion中,如果再次遇到了已经遇到过的node,那么有环- 即:代表是否已被当前节点启动的DFS访问
- 如果是找一个环哪具体包含哪些节点 or 计算cycle的大小,可以加一个
prev[cur] = prevNode来保存每一步怎么走的
def isCyclicDirectedGraph(graph: List[List[int]], n: int) -> bool:
def dfs(cur) -> bool: # cur启动的DFS中有没有环
if recStack[cur]: # 被当前node启动的dfs遍历过
return True
if visited[cur]: # 被其他node启动的dfs遍历过
return False
visited[cur] = True
recStack[cur] = True
for next in graph[cur]:
if dfs(next):
return True
recStack[cur] = False # backtrack
return False
visited = [False] * n
recStack = [False] * n
for i in range(n):
if not visited[i] and dfs(i):
return True
return False
2127. 参加会议的最多员工数(Hard)
Solu:DFS + 双array
- step 1: DFS+双array找最大环
- 因为size = 2的cycles们可以拼在一桌说,所以要单独记下
- step 2: DFS找size = 2的每个cycle中的两个node分别可以延长出的最长path是多少,把他们都拼到一桌上
- step 3:
res = max{最大环,所有size = 2的cycles及其extended paths的拼接}
Code:
class Solution:
def __init__(self):
self.sizeTwoCycle = []
self.maxCycle = 0
self.favorite = None
def maximumInvitations(self, favorite: List[int]) -> int:
def buildGraph():
beloved = {i: [] for i in range(len(favorite))}
for i, fav in enumerate(favorite): # 被谁喜欢
beloved[fav].append(i)
return beloved
beloved = buildGraph()
self.favorite = favorite
self.countCycle(beloved)
print(self.maxCycle)
# 尝试在size=2的cycle中,对两个node都尝试extend一条最长的path (NOT cycle)
return max(self.maxCycle, self.sizeTwoExtend(beloved))
def countCycle(self, beloved) -> None: # find the max cycle size, and store all size=2 cycles
def dfs(cur, cycleSize):
if recStack[cur]: # cycle found
self.maxCycle = max(self.maxCycle, cycleSize)
if cycleSize == 2:
self.sizeTwoCycle.append((cur, self.favorite[cur]))
return
if visited[cur]:
return
visited[cur] = True
recStack[cur] = True
for next in beloved[cur]:
dfs(next, cycleSize + 1)
recStack[cur] = False # backtracking
visited = [False] * len(beloved)
recStack = [False] * len(beloved)
for i in range(len(beloved)):
if not visited[i]:
dfs(i, 0)
def sizeTwoExtend(self, beloved) -> int: # extend the longest path from both two nodes in size=2 cycle
def dfs(cur) -> int: # longest path from cur
if visited[cur]:
return 0
res = 0
for next in beloved[cur]:
res = max(res, dfs(next))
return res + 1
visited = [False] * len(beloved)
res = 0
for i, j in self.sizeTwoCycle:
# handle i
visited[j] = True
iLongest = dfs(i)
visited[j] = False # backtracking
# handle j
visited[i] = True
jLongest = dfs(j)
visited[i] = False # backtracking
res += iLongest + jLongest # 不会重复加上,因为每个人只喜欢一个人,所以不可能有同一个node出现在多个size=2 cycle中
return res
Reference: