題目會給我們一個輸入陣列prerequisites,每個pair代表兩個課程之間的先修關係,和課程總數numCourses。
題目問我們這組課程表是否能依照順序修完所有的課程?
如果可以,返回True。
如果不行,代表有擋修形成死結,無法依照順序修完所有的課程,返回False。
Example 1:
Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Explanation: There are a total of 2 courses to take.
To take course 1 you should have finished course 0. So it is possible.
Example 2:
Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take.
To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.
0, 1兩個課程彼此擋修,形成互卡的死結,因此無法依照順序修完所有的課程。
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= a
i
, b
i
< numCourses
本文章將介紹兩種方法。
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# Constant defined for course state
NOT_CHECKED, CHECKING, COMPLETED = 0, 1, 2
# -------------------------------
def has_deadlock( course )->bool:
if course_state[course] != NOT_CHECKED:
# There is a cycle(i.e., deadlock ) in prerequisites
return course_state[course] == CHECKING
# update current course as checking
course_state[course] = CHECKING
# check pre_course in DFS and detect whether there is deadlock
for pre_course in requirement[course]:
if has_deadlock( pre_course ):
# deadlock is found, impossible to finish all courses
return True
# update current course as completed
course_state[course] = COMPLETED
return False
# -------------------------------
# each course maintain a list of its own prerequisites
requirement = collections.defaultdict( list )
for course, pre_course in prerequisites:
requirement[course].append( pre_course )
# each course maintain a state among {NOT_CHECKED, CHECKING, COMPLETED}
# Initial state is NOT_CHECKED
course_state = [ NOT_CHECKED for _ in range(numCourses) ]
# Launch cycle (i.e., deadlock ) detection in DFS
for course_idx in range(0, numCourses):
if has_deadlock(course_idx):
# deadlock is found, impossible to finish all courses
return False
# we can finish all course with required order
return True
另一種就是Topological sort拓樸排序。
建造一個Queue,從沒有擋修的課程(in_degree = 0的節點)開始進行廣度優先搜索,移除這一輪沒有擋修的課程和對應的out-going edge,移除的同時,把下一輪沒有擋修的課程加入Queue中,反覆進行迭代。
最後,如果所有課程都移除完畢,而且沒有剩餘的課程,代表可以依照順序修完所有課程。否則,代表存在有擋修互卡的死結。
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
# each course maintain a list of its own dependency
relation = defaultdict( list )
# in degree of each course
# in degree stands for the number of preliminary courses
in_degree = defaultdict( int )
for course, pre_course in prerequisites:
# update relation
relation[pre_course].append( course )
# update in_degree
in_degree[course] += 1
# Topological sort on in_degree
bfs_q = deque()
for course_idx in range(0, numCourses):
if in_degree[course_idx] == 0:
bfs_q.append(course_idx)
finish_count = 0
while bfs_q:
# Finish current course
cur_course = bfs_q.popleft()
finish_count += 1
# Unlock some courses related to current course
for course in relation[cur_course]:
in_degree[course] -= 1
if in_degree[course] == 0:
# this course can be taken next round if it doesn't have preliminary course anymore
bfs_q.append( course )
return finish_count == numCourses
時間複雜度:
O( V+E )
兩種方法皆為O(V+E),點至多拜訪一次,邊也是至多拜訪一次。
空間複雜度:
O( V+E )
兩種方法皆為O(V+E),包含對應的DFS遞迴深度 + table大小,或者BFS Queue長度 + table大小
Reference:
[1] Python by DFS//BFS and cycle detection [w/ Graph ] - Course Schedule - LeetCode