edge_matrix =[[1,2],# children ids for node 0[2],# children ids for node 1[],# children ids for node 2]# or use a hashvertice_children_map ={0:[1,2],1:[2]}
【TODO】对于问题的潜在解之间存在重复部分的情况,DFS的return值必须体现subproblem的结果,应该将 visited 改成 hash 来实现考虑memoization,也就是 id 作为hash的key,return值也就是子问题的解,作为hash的value。这一点在Chapter 5中会详细描述。
e.g.1There are N rooms and you start in room 0. Each room may have some keys to access the next room. Return true if and only if you can enter every room. (LeetCode: Keys and Rooms)
e.g.2There are a total of numCourses courses you have to take, labeled from 0 to numCourses-1.Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]. Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses? ( LeetCode: Course Schedule )
e.g.1 There are a total of n courses you have to take labelled from 0 to n - 1. Some courses may have prerequisites, for example, if prerequisites[i] = [ai, bi] this means you must take the course bi before the course ai. Given the total number of courses numCourses and a list of the prerequisite pairs, return the ordering of courses you should take to finish all courses. ( LeetCode: Course Schedule II )
e.g.2 There is a new alien language which uses the latin alphabet. However, the order among letters are unknown to you. You receive a list of non-empty words from the dictionary, where words are sorted lexicographically by the rules of this new language. Derive the order of letters in this language. (LeetCode: Alien Dictionary)
edge_matrix = [
[ 0, 1, 1], # [1,2] are children ids for node 0
[ 0, 0, 1], # [2] are children ids for node 1
[ ], # [] are children ids for node 2
]
vertex_ids = range(0, 3)
edges = [
[0, 1], # node 0 -> node 1
[0, 2], # node 0 -> node 2
[1, 2], # node 1
]
# we need to iterate edges in order to get the children for each node
vertex_children_map = defaultdict(list)
for edge in edges:
vertex_children_map[edge[0]].append(edge[1])
# vertex_children_map will now behave the same as 4.4.2
# [Template] (assume we have the standard representation of id => child_ids)
class Graph:
def __init__(self, ids, vertex_children_map):
self.ids = ids
# could be 2d array or hash of arrays
self.vertex_children_map = vertext_children_map
self.visited = set() # OrderedDict or Dict if we need the path
self.visiting = set()
def traverse(self):
try:
for id in self.ids:
self.dfs(id) # or bfs(id)
except Exception:
return # optional
return
"""
DFS
"""
def dfs(self, id: int):
if id in visited: return
if id in visiting:
raise Exception('has cycle')
visiting.add(id)
child_ids = vertext_children_map[id]
for child_id in child_ids:
dfs(child_id)
visiting.remove(id)
visited.add(id) # visited[id] = id for path using OrderedDict
return
"""
BFS without cycle detection
"""
def bfs(self, id: int):
if id in visited: return
queue = [id]
visited.add(id)
while len(queue) != 0:
id = queue.pop(0)
child_ids = vertex_children_map[id]
for child_id in child_ids:
if id not in visited:
# use parent[child_id] = id for path
# use depth[child_id] = depth[child_id] + 1 for depth
visited.add(child_id)
queue.append(child_id)
return
class Solution:
def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
self.visited = set()
self.visiting = set() # not entirely necessary, just as demo
self.vertex_children_map = rooms
self.traverse(0)
return len(self.visited) == len(rooms)
def traverse(self, id):
if id in self.visited:
return
if id in self.visiting:
return
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited.add(id)
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
self.ids = range(numCourses)
self.vertex_children_map = defaultdict(list)
for pair in prerequisites:
self.vertex_children_map[pair[1]].append(pair[0])
self.visited = set()
self.visiting = set()
try:
for id in self.ids:
self.traverse(id)
except Exception:
return False
return len(self.visited) == len(self.ids)
def traverse(self, id: int):
if id in self.visited:
return
if id in self.visiting:
raise Exception('has cycle')
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited.add(id)
# *Kahn's Algorithm
L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edge
while S is not empty do
remove a node x from S
add x to L
for each x's children y do
decrement y's indegree count
if y has 0 indegree then
insert y into S
if graph has edges then
return error (graph has at least one cycle)
else
return L (a topologically sorted order)
# Python Code
class Graph:
def __init__(self, ids, edges: List[List[int]]):
self.ids = ids
# could be 2d array or hash of arrays
self.edges = edges
self.indegrees = Counter()
self.vertex_children_map = defaultdict(list)
for edge in edges:
self.indegrees[edge[1]] += 1
self.vertex_children_map[edge[0]].append(edge[1])
self.visited = []
def traverse(self):
queue = []
for ids in self.ids:
if self.indegrees[id] == 0:
queue.append(id)
while len(queue) != 0:
id = queue.pop(0)
visited.append(id)
child_ids = vertex_children_map[id]
for child_id in child_ids:
indegrees[id] -= 1
if indegrees[id] == 0:
queue.append(id)
return visited if visited.length == self.ids.length else None # has cycle
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
self.ids = range(numCourses)
self.vertex_children_map = defaultdict(list)
for pair in prerequisites:
self.vertex_children_map[pair[1]].append(pair[0])
self.visited = OrderedDict() # change from course schedule I
self.visiting = set()
try:
for id in self.ids:
self.traverse(id)
except Exception:
return [] # change from course schedule I
return reversed(self.visited.keys()) # change from course schedule I
def traverse(self, id: int):
if id in self.visited:
return
if id in self.visiting:
raise Exception('has cycle')
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited[id] = id # change from course schedule I
class Solution:
def get_edge_from_words(self, word_a, word_b) -> Optional[List[str]]:
length = min(len(word_a), len(word_b))
for idx in range(length):
if word_a[idx] != word_b[idx]:
return [word_a[idx], word_b[idx]]
if len(word_a) > len(word_b):
raise Exception(
'{} should not appear before {}'.format(word_a, word_b))
return None
def alienOrder(self, words: List[str]) -> str:
self.vertex_children_map = defaultdict(list)
self.ids = set()
for word in words:
self.ids.update(list(word))
try:
for idx in range(1, len(words)):
edge = self.get_edge_from_words(words[idx - 1], words[idx])
if edge:
self.vertex_children_map[edge[0]].append(edge[1])
self.visited = OrderedDict()
self.visiting = set()
for id in self.ids:
self.traverse(id)
except Exception:
return ''
return ''.join(reversed(self.visited.keys()))
def traverse(self, id):
if id in self.visited:
return
if id in self.visiting:
raise Exception('cycle ocurrs')
self.visiting.add(id)
child_ids = self.vertex_children_map[id]
for child_id in child_ids:
self.traverse(child_id)
self.visiting.remove(id)
self.visited[id] = id