- 拓扑排序
- 并查集
2021/5/11
785. 判断二分图
这道题真的是烦,明明知道思路了,总是超出递归长度,关键点:
- dfs 函数遇到fasle怎么 直接return 回去 结束遍历
自己这样写一是有错误,混了dfs和bfs,二是超出递归长度
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
n=len(graph)
self.flag=True
# 给节点分成两组 节点和一阶邻居要分开
# idx是下一个要分类的点
set1=[False]*n
set2=[False]*n
# 返回true 说明当前还没有冲突
def dfs(idx):
if(set1[idx] and set2[idx]):
return False
# 节点idx 还未分类;分到1中去
if(not set1[idx] and not set2[idx]):
set1[idx]=True
for v in graph[idx]:
set2[v]=True
if(set1[v]):
return False
# 节点idx 被分类了
else:
# idx 在1中
if(set1[idx]):
for v in graph[idx]:
set2[v]=True
if(set1[v]):
return False
# idx 在2中
else:
for v in graph[idx]:
set1[v]=True
if(set2[v]):
return False
# 遍历一阶邻居
for v in graph[idx]:
if(not dfs(v)):
return False
return True
for i in range(n):
if(not set1[i] and not set2[i]):
if(not dfs(i)):
return False
return True
然后理清楚怎么终止dfs的return 方法之后,还是超出遍历
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
n=len(graph)
self.flag=True
# 给节点分成两组 节点和一阶邻居要分开
# 0,1,2表示未分组,第一组,第二组
mark=[0]*n
# idx是要分类的点
def dfs(idx):
# 如果当前节点未分类 且遍历到了 直接分到第一组
# 也就是第一个点和所有不联通图上的第一个遍历点 所以是正确的
# if bfs,就不对了
if(mark[idx]==0):
mark[idx]=1
markNeighbor=2 if mark[idx]==1 else 1
for v in graph[idx]:
# 判断是否出现邻居点 被分组 且当前分组冲突的情况
if(mark[v]==mark[idx]):
self.falg=False
return # 终止遍历 返回上一depth
# 没有分组冲突 进行遍历
mark[v]=markNeighbor
dfs(v)
# 重点!!!! 遍历完depth+1,depth+2,...终点之后
# 判断flag 如果false 那么从当前depth 终止这条path
if(not self.falg):
return
# 用i in range 是保证不联通图的情况下
for i in range(n):
dfs(i)
if(not self.flag):
return False
return True
class Solution:
def isBipartite(self, graph: List[List[int]]) -> bool:
n=len(graph)
self.flag=True
# 给节点分成两组 节点和一阶邻居要分开
# 0,1,2表示未分组,第一组,第二组
mark=[0]*n
# idx是要分类的点
def dfs(idx):
# 如果当前节点未分类 且遍历到了 直接分到第一组
# 也就是第一个点和所有不联通图上的第一个遍历点 所以是正确的
# if bfs,就不对了
markNeighbor=2 if mark[idx]==1 else 1
for v in graph[idx]:
# 判断是否出现邻居点当前分组冲突的情况
if(mark[v]==mark[idx]):
self.flag=False
return # 终止遍历 返回上一depth
# 邻居v没被分组
elif(mark[v]==0):
# 没有分组冲突 进行遍历
mark[v]=markNeighbor
dfs(v)
# 重点!!!! 遍历完depth+1,depth+2,...终点之后
# 判断flag 如果false 那么从当前depth 终止这条path
if(not self.flag):
return
# 用i in range 是保证不联通图的情况下
for i in range(n):
#print('节点{}'.format(i))
if(mark[i]==0):
mark[i]=1
dfs(i)
if(not self.flag):
return False
return self.flag
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# 相当于一个有向图上的节点 不能沿着有向方向出现环
mark=set()
from collections import defaultdict
graph=defaultdict(set)
for v in prerequisites:
graph[v[0]].add(v[1])
# 每个节点可达的节点
#arrive=defaultdict(set)
flag=True
# 遍历dfs 从当前节点 继续探索path
def dfs(curNode,curPath):
mark.add(curNode)
nonlocal flag
if(curNode in curPath):
flag=False
return # 一旦发现有环了 就终止遍历
# # 如果preNode有先验 就dfs
for preNode in graph[curNode]:
#if(preNode in graph.keys()):
# return
#注意这里的return 不应该加
if(preNode in graph.keys()):
dfs(preNode,curPath+[curNode])
if(not flag):
return # 终止这一条path
for node in graph.keys():
if(node not in mark):
dfs(node,[])
if(not flag):
return False
return flag
自己的倒数第二个实例超时间了,还是题解巧妙,自己使用mark集合来判断哪个节点完全访问过了,curPath存储当前路径的所有节点;但是题解的巧妙之处在于
- 使用数组表示mark,这样可以用0,1,2表示节点的三种状态:未访问;正在访问;完全访问结束。那么即mark就可以即当作visied数组;又当作path的集合
- 只要邻居节点一出现mark[i]=1,那么相当于又遇到正在访问的点了,即有环,return False;很巧妙
DFS
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# 相当于一个有向图上的节点 不能沿着有向方向出现环
from collections import defaultdict
graph=defaultdict(set)
for v in prerequisites:
graph[v[0]].add(v[1])
flag=True
# 0未访问;1正在访问;2访问完了
mark=[0]*numCourses
# 遍历dfs 从当前节点 继续探索path
def dfs(curNode):
if(mark[curNode]==2 or curNode not in graph.keys()):
return
mark[curNode]=1
nonlocal flag
for preNode in graph[curNode]:
if(mark[preNode]==0):
dfs(preNode)
if(not flag):
return # 终止这一条path
elif(mark[preNode]==1):
flag=False
return
mark[curNode]=2
for node in graph.keys():
if(mark[node]==0):
dfs(node)
if(not flag):
return False
return flag
- 自己注意一个错误:想跳过一些情况时,就是当节点不满足条件时,注意return到底加不加!! 注意这里的return 不应该加 一return后面还未遍历到的同阶节点得不到dfs
for preNode in graph[curNode]:
#if(preNode in graph.keys()):
# return
#注意这里的return 不应该加 一return后面还未遍历到的同阶节点得不到dfs
if(preNode in graph.keys()):
dfs(preNode,curPath+[curNode])
if(not flag):
return # 终止这一条path
BFS
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# 相当于一个有向图上的节点 不能沿着有向方向出现环
from collections import defaultdict
from collections import deque
graph=defaultdict(set)
preNum=[0]*numCourses # i需要pre的数量
for v in prerequisites:
graph[v[0]].add(v[1])
preNum[v[1]]+=1
# bfs 先得到无需先验的课程 然后一步一步找到可以完成的课程
q=deque([idx for idx in range(numCourses) if preNum[idx]==0])
cnt=len(q)
while(q):
cur=q.popleft()
# 遍历cur的邻居 pre-1
if(cur in graph.keys()):
for v in graph[cur]:
preNum[v]-=1
if(preNum[v]==0):
q.append(v)
cnt+=1
return cnt==numCourses
2021/5/14
684. 冗余连接
并查集,这题卡了好久,结果是一种经典算法
自己下意识要写dfs 结果写半天也不对↓这个不对
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
from collections import defaultdict
graph=defaultdict(set)
for e in edges:
graph[e[0]].add(e[1])
graph[e[1]].add(e[0])
mark=[0]*(len(edges)+1)
self.result=[]
# dfs找环
def dfs(cur,curPath):
print('dfs')
print(curPath)
# 正在visit
mark[cur]=1
if(cur in graph.keys()):
print('如果在grapg中')
print(curPath)
# 遍历邻居节点
for negbor in graph[cur]:
if(negbor!=cur):
print('negbor')
print(curPath)
if(mark[negbor]==1 or mark[negbor]==2):
print('ok')
self.result=curPath+[(cur,negbor)]
print(self.result)
return
else:
dfs(negbor,curPath+[(cur,negbor)])
if(len(self.result)>0):
return
mark[cur]=2
dfs(1,[])
print(self.result)
return self.result
并查集
可以理解为每个节点都连着一个根节点(代表人物);当有一条边时,判断当前边的两个节点n1,n2的根节点是否相同:
- n1 n2根节点相同,表明n1 n2本来就在一社区里,本来就可以通过各种边相连;然后当前边又连了,说明出现了环(之间就能连 现在又连了)
- n1 n2根节点不同,说明n1 n2之前不相连,现在通过当前边连起来了,那么就将n2的根节点连接到n1的根节点上(反之也可以)
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
from collections import defaultdict
#graph=defaultdict(set)
# 并查集 父节点都是自己
parent=list(range(len(edges)+1)) # 0,1,2...,n
# 返回idx的根节点
def find(idx):
# 父节点=自己 说明是根节点
if(parent[idx]==idx):
return idx
else:
return find(parent[parent[idx]])
# 根节点不同 合并
def union(n1,n2):
# n1,n2的根节点都是自己
# 把n2的根节点连接到n1即可
parent[find(n2)]=find(n1)
for n1,n2 in edges:
# 父节点不同 就连接
# r1,r2=find(n1),find(n2)
if(find(n1)!=find(n2)):
union(n1,n2)
else:
return [n1,n2]
# if(r1!=r2):
# parent[r2]=r1
# else:
# return [n1,n2]
return []
210. 课程表 II
这道题竟然卡在了 返回这个路径上。。。 自己写了一个rank列表 想着每走一步 rank+1;但是不知道哪里出现了问题(应该是从有先验的课倒着遍历 选max先验rank+1;自己是从无先验的开始遍历 rank+1,会忽略真正的排序情况),最后几个例子过不去
- 实际上只要定义一个stack,每当到达一个终点 就append进去就好了。。
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
# 有向图 如果存在环就[] 不存在环就返回顺序
from collections import defaultdict
graph=defaultdict(set)
# k-v:k是v的先验
for e in prerequisites:
graph[e[1]].add(e[0])
self.flag=True
# 0表示未visit;1表示正在visit;2表示完全visit过了
mark=[0]*numCourses
rank=[]
# dfs遍历 判断是否有环
def dfs(cur):
mark[cur]=1
# 如果有先验 就dfs遍历邻居
if(cur in graph.keys()):
for v in graph[cur]:
# 遇到环了
if(mark[v]==1):
self.flag=False
return
# 遇到mark[v]=2时 不用判断了 因为v没有环 如果有早就返回了
elif(mark[v]==0):
dfs(v)
if(not self.flag):
return # 遇到环 终止这条path
mark[cur]=2
# 每当完成一个说明到达一个path的终点
rank.append(cur)
for i in range(numCourses):
if(mark[i]==0):
dfs(i)
if(not self.flag):
return []
return rank[::-1]