说明:前五题基本都用了两种方法分别解答: DFS 和 拓扑排序 DFS很绕,拓扑排序理解起来更简单,一般都首选拓扑排序
但是像例六这种寻找所有可能路径之类用到回溯会更方便的题,就用DFS
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]] 输出:true 解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
class Solution(object):
def canFinish(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
adj=collections.defaultdict(list)
deg=collections.Counter()
for prerequisite in prerequisites:
a,b=prerequisite[0],prerequisite[1]
adj[b].append(a)
deg[a]+=1
q = collections.deque()
for u in range(numCourses):
if deg[u]==0:
q.append(u)
res=[]
while len(q)>0:
tmp=q.popleft()
res.append(tmp)
for v in adj[tmp]:
deg[v]-=1
if deg[v]==0:
q.append(v)
return len(res)==numCourses
class Solution(object):
def canFinish(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: bool
"""
graph=[[] for _ in range(numCourses)]
indegree=[0 for _ in range(numCourses)]
for cur,pre in prerequisites:
graph[pre].append(cur)
indegree[cur]+=1
deque=collections.deque()
for i in range(len(indegree)):
if not indegree[i]:
deque.append(i)
while deque:
pre=deque.popleft()
numCourses-=1
for cur in graph[pre]:
indegree[cur]-=1
if not indegree[cur]:
deque.append(cur)
return not numCourses
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。 返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]] 输出:[0,1] 解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。 示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] 输出:[0,2,1,3] 解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
Python3 力扣官方DFS解法 比较绕 这里想用python2对代码进行改写,主要改的部分是valid变量。它在函数中的作用是记录是否有环,一旦更改为False就不应再变,即使是递归的回溯也不应该改变它的值。 官方代码用的是nonlocal的声明 。它的作用简单说明:在外部函数先进行声明,在内部函数进行nonlocal声明,这样在内部函数中的变量与外部函数中的同名是同一个变量。 nonlocal说明
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
edges = collections.defaultdict(list)
visited = [0] * numCourses
result = list()
valid = True
for info in prerequisites:
edges[info[1]].append(info[0])
def dfs(u: int):
nonlocal valid
visited[u] = 1
for v in edges[u]:
if visited[v] == 0:
dfs(v)
if not valid:
return
elif visited[v] == 1:
valid = False
return
visited[u] = 2
result.append(u)
for i in range(numCourses):
if valid and not visited[i]:
dfs(i)
if not valid:
return list()
return result[::-1]
python2版本(通过def init(self):把self.valid声明为全局变量)
class Solution(object):
def __init__(self):
self.valid=True
def dfs(self,u,visited,edges,result):
visited[u] = 1
for v in edges[u]:
if visited[v] == 0:
self.dfs(v,visited,edges,result)
if not self.valid:
return
elif visited[v] == 1:
self.valid = False
return
visited[u] = 2
result.append(u)
def findOrder(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: List[int]
"""
edges = collections.defaultdict(list)
visited = [0] * numCourses
result = list()
for info in prerequisites:
edges[info[1]].append(info[0])
for i in range(numCourses):
if self.valid and not visited[i]:
self.dfs(i,visited,edges,result)
if not self.valid:
return list()
return result[::-1]
但是平心而论,DFS做关于图论的题实在太麻烦了,所以还是推荐拓扑排序的解法。拓扑排序的定义是BFS和贪心算法应用于有向图的的专有名词。 「拓扑排序」的一个附加效果是:能够顺带检测有向图中是否存在环。如果优先图中,存在环,拓扑排序不能继续得到入度值为 0 的节点,退出循环,此时图中存在没有遍历到的节点,说明图中存在环。(顺丰科技竞赛第一题)
基本代码都换汤不换药
class Solution(object):
def findOrder(self, numCourses, prerequisites):
"""
:type numCourses: int
:type prerequisites: List[List[int]]
:rtype: List[int]
"""
adj=[[] for _ in range(numCourses)]
indeg=[0 for _ in range(numCourses)]
for prerequisite in prerequisites:
adj[prerequisite[1]].append(prerequisite[0])
indeg[prerequisite[0]]+=1
from collections import deque
queue=deque()
for i in range(numCourses):
if indeg[i]==0:
queue.append(i)
res=[]
while queue:
top=queue.pop()
res.append(top)
for relate in adj[top]:
indeg[relate]-=1
if indeg[relate]==0:
queue.append(relate)
if len(res)!=numCourses:
return []
else:
return res
有一组 n 个人作为实验对象,从 0 到 n - 1 编号,其中每个人都有不同数目的钱,以及不同程度的安静值(quietness)。为了方便起见,我们将编号为 x 的人简称为 "person x "。
给你一个数组 richer ,其中 richer[i] = [ai, bi] 表示 person ai 比 person bi 更有钱。另给你一个整数数组 quiet ,其中 quiet[i] 是 person i 的安静值。richer 中所给出的数据 逻辑自洽(也就是说,在 person x 比 person y 更有钱的同时,不会出现 person y 比 person x 更有钱的情况 )。
现在,返回一个整数数组 answer 作为答案,其中 answer[x] = y 的前提是,在所有拥有的钱肯定不少于 person x 的人中,person y 是最安静的人(也就是安静值 quiet[y] 最小的人)。
示例 1:
输入:richer = [[1,0],[2,1],[3,1],[3,7],[4,3],[5,3],[6,3]], quiet = [3,2,5,4,6,1,7,0] 输出:[5,5,2,5,4,5,6,7] 解释: answer[0] = 5, person 5 比 person 3 有更多的钱,person 3 比 person 1 有更多的钱,person 1 比 person 0 有更多的钱。 唯一较为安静(有较低的安静值 quiet[x])的人是 person 7, 但是目前还不清楚他是否比 person 0 更有钱。 answer[7] = 7, 在所有拥有的钱肯定不少于 person 7 的人中(这可能包括 person 3,4,5,6 以及 7), 最安静(有较低安静值 quiet[x])的人是 person 7。 其他的答案也可以用类似的推理来解释。
DFS解法
class Solution(object):
def loudAndRich(self, richer, quiet):
"""
:type richer: List[List[int]]
:type quiet: List[int]
:rtype: List[int]
"""
n=len(quiet)
adj=[[] for _ in range(n)]
for r in richer:
adj[r[1]].append(r[0])
ans=[-1]*n
def dfs(x):
if ans[x]!=-1:
return
ans[x]=x
for y in adj[x]:
dfs(y)
if quiet[ans[y]]<quiet[ans[x]]:
ans[x]=ans[y]
for i in range(n):
dfs(i)
return ans
拓扑排序解法
class Solution(object):
def loudAndRich(self, richer, quiet):
"""
:type richer: List[List[int]]
:type quiet: List[int]
:rtype: List[int]
"""
n=len(quiet)
adj=[[] for _ in range(n)]
indeg=[0 for _ in range(n)]
for r in richer:
adj[r[0]].append(r[1])
indeg[r[1]]+=1
from collections import deque
queue=deque()
for i in range(n):
if indeg[i]==0:
queue.append(i)
ans=list(range(n))
while queue:
top=queue.pop()
for relate in adj[top]:
if quiet[ans[top]]<quiet[ans[relate]]:
ans[relate]=ans[top]
indeg[relate]-=1
if indeg[relate]==0:
queue.append(relate)
return ans
小镇里有 n 个人,按从 1 到 n 的顺序编号。传言称,这些人中有一个暗地里是小镇法官。
如果小镇法官真的存在,那么:
小镇法官不会信任任何人。 每个人(除了小镇法官)都信任这位小镇法官。 只有一个人同时满足属性 1 和属性 2 。 给你一个数组 trust ,其中 trust[i] = [ai, bi] 表示编号为 ai 的人信任编号为 bi 的人。
如果小镇法官存在并且可以确定他的身份,请返回该法官的编号;否则,返回 -1 。
示例 1:
输入:n = 2, trust = [[1,2]] 输出:2
class Solution(object):
def findJudge(self, n, trust):
"""
:type n: int
:type trust: List[List[int]]
:rtype: int
"""
indeg=[0 for _ in range(n)]
outdeg=[0 for _ in range(n)]
for t in trust:
indeg[t[1]-1]+=1
outdeg[t[0]-1]+=1
for i in range(n):
if indeg[i]==n-1 and outdeg[i]==0:
return i+1
return -1
有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。
当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。
给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false。
示例 1:
输入:rooms = [[1],[2],[3],[]] 输出:true 解释: 我们从 0 号房间开始,拿到钥匙 1。 之后我们去 1 号房间,拿到钥匙 2。 然后我们去 2 号房间,拿到钥匙 3。 最后我们去了 3 号房间。 由于我们能够进入每个房间,我们返回 true。
class Solution(object):
def canVisitAllRooms(self, rooms):
"""
:type rooms: List[List[int]]
:rtype: bool
"""
n=len(rooms)
visited=[False for _ in range(n)]
def dfs(key,visited,rooms):
if visited[key]:
return
visited[key]=True
m=len(rooms[key])
for i in range(m):
dfs(rooms[key][i],visited,rooms)
dfs(0,visited,rooms)
for i in range(n):
if not visited[i]:
return False
return True
拓扑排序方法
class Solution(object):
def canVisitAllRooms(self, rooms):
"""
:type rooms: List[List[int]]
:rtype: bool
"""
n=len(rooms)
visited=[False for _ in range(n)]
from collections import deque
queue=deque()
queue.append(rooms[0])
visited[0]=True
while queue:
top=queue.pop()
if not top:
continue
for i in top:
if visited[i]:
continue
queue.append(rooms[i])
visited[i]=True
for i in range(n):
if not visited[i]:
return False
return True
给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。
输入:graph = [[1,2],[3],[3],[]] 输出:[[0,1,3],[0,2,3]] 解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3
class Solution(object):
def allPathsSourceTarget(self, graph):
"""
:type graph: List[List[int]]
:rtype: List[List[int]]
"""
n=len(graph)
ans=[]
def dfs(key,graph,path):
if key>=n-1:
ans.append(path[:])
return
for relate in graph[key]:
dfs(relate,graph,path+[relate])
dfs(0,graph,[0])
return ans
有一个有 n 个节点的有向图,节点按 0 到 n - 1 编号。图由一个 索引从 0 开始 的 2D 整数数组 graph表示, graph[i]是与节点 i 相邻的节点的整数数组,这意味着从节点 i 到 graph[i]中的每个节点都有一条边。
如果一个节点没有连出的有向边,则它是 终端节点 。如果没有出边,则节点为终端节点。如果从该节点开始的所有可能路径都通向 终端节点 ,则该节点为 安全节点 。
返回一个由图中所有 安全节点 组成的数组作为答案。答案数组中的元素应当按 升序 排列。
输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]] 输出:[2,4,5,6] 解释:示意图如上。 节点 5 和节点 6 是终端节点,因为它们都没有出边。 从节点 2、4、5 和 6 开始的所有路径都指向节点 5 或 6 。
class Solution(object):
def eventualSafeNodes(self, graph):
"""
:type graph: List[List[int]]
:rtype: List[int]
"""
n=len(graph)
indeg=[0 for _ in range(n)]
adj=[[] for _ in range(n)]
for x,ys in enumerate(graph):
for y in ys:
adj[y].append(x)
indeg[x]+=1
from collections import deque
queue=deque()
for i in range(n):
if indeg[i]==0:
queue.append(i)
while queue:
top=queue.pop()
for relate in adj[top]:
indeg[relate]-=1
if indeg[relate]==0:
queue.append(relate)
return [i for i, d in enumerate(indeg) if d == 0]
|