圖論應用經典 課程表 Course Schedule_Leetcode #207

更新於 發佈於 閱讀時間約 12 分鐘

題目敘述

題目會給我們一個輸入陣列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 <= ai, bi < numCourses
  • All the pairs prerequisites[i] are unique.

演算法 DFS

本文章將介紹兩種方法。

  1. 可以用DFS的cycle detection環路偵測演算法來偵測是否有擋修互卡的死結。
  2. 可以用BFS的Topological sort拓樸排序,來檢查是存在擋修互卡的死結。


  1. 可以用DFS的cycle detection環路偵測演算法
    每個節點對應到課程,每個先修關係對應到一條邊。
    造一個loop,從編號小的節點開始進行DFS深度優先拜訪。
    假如遇到當下這個節點是Checking狀態,代表存在死結,無法依照順序修完所有的課程。
    假如當下這個節點尚未檢查過,把自己標記成Checking,接著遞迴呼叫DFS拜訪所有鄰居節點。
    最後,假如都順利完成,把當下這個節點(也就是這門課程)標記為Completed。


image

image

image

image


image

image

image

image


程式碼 DFS的cycle detection環路偵測演算法

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

演算法 BFS

另一種就是Topological sort拓樸排序。

建造一個Queue,從沒有擋修的課程(in_degree = 0的節點)開始進行廣度優先搜索,移除這一輪沒有擋修的課程和對應的out-going edge,移除的同時,把下一輪沒有擋修的課程加入Queue中,反覆進行迭代。

最後,如果所有課程都移除完畢,而且沒有剩餘的課程,代表可以依照順序修完所有課程。否則,代表存在有擋修互卡的死結。


程式碼 BFS的Topological sort拓樸排序

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大小


關鍵知識點

  1. 把課程先修關係抽象化,轉為圖論中的節點(課程)和邊(先修關係),把課程擋修的問題轉化為是否存在Cycle環路的問題


  1. 圖論中的題目,假如沒有特殊限制或者還沒有具體的解題想法,先聯想到DFS和BFS,這兩個可以說是圖論中最泛用的演算法模板

Reference:

[1] Python by DFS//BFS and cycle detection [w/ Graph ] - Course Schedule - LeetCode


留言
avatar-img
留言分享你的想法!
小松鼠 繼續加油 雖然不是股市投資等等直接跟錢有關的文章 不是很熱門 但是只要努力 規劃跟方向 佈局都持續檢討 將來收穫很可觀 我知道專業文章比較冷門 因為懂門道之人本來就是少數 但是醫殿成功 財力會迅速上升 不要放棄 小松鼠 有空歡迎來我那裡逛逛天照真 敬上
avatar-img
小松鼠的演算法樂園
96會員
427內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
2024/09/26
Leetcode 729. My Calendar I 給定一個行事曆的class定義和行程安排的介面interface。 請完成下列function 1.建構子MyCalendar() 初始化MyCalendar物件 2.boolean book(int start, int end) 插入新行程
Thumbnail
2024/09/26
Leetcode 729. My Calendar I 給定一個行事曆的class定義和行程安排的介面interface。 請完成下列function 1.建構子MyCalendar() 初始化MyCalendar物件 2.boolean book(int start, int end) 插入新行程
Thumbnail
2024/09/10
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
2024/09/10
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
2024/09/09
2326. Spiral Matrix IV 題目給定一個Linked list和對應的矩陣高度m、寬度n。 請依照順時針的拜訪順序, 從左上角出發,依照次序把Linked List的內容填到矩陣裡。 如果有剩餘不足的空位,就填補-1。 最後將填補好的矩陣返回作為答案。
Thumbnail
2024/09/09
2326. Spiral Matrix IV 題目給定一個Linked list和對應的矩陣高度m、寬度n。 請依照順時針的拜訪順序, 從左上角出發,依照次序把Linked List的內容填到矩陣裡。 如果有剩餘不足的空位,就填補-1。 最後將填補好的矩陣返回作為答案。
Thumbnail
看更多
你可能也想看
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
題目敘述 題目會給我們一個輸入陣列prerequisites,每個pair代表兩個課程之間的先修關係,和課程總數numCourses。 題目問我們這組課程表是否能依照順序修完所有的課程? 如果可以,返回True。 如果不行,代表有擋修形成死結,無法依照順序修完所有的課程,返回False。
Thumbnail
題目敘述 題目會給我們一個輸入陣列prerequisites,每個pair代表兩個課程之間的先修關係,和課程總數numCourses。 題目問我們這組課程表是否能依照順序修完所有的課程? 如果可以,返回True。 如果不行,代表有擋修形成死結,無法依照順序修完所有的課程,返回False。
Thumbnail
題目敘述 題目會給我們一張Courses資料表,裡面分別有student、class等欄位。其中(student, class) 是這張資料表的複合主鍵Primary key pair。 要求我們,以課程做分群,列出至少有五位同學的課程。 輸出的順序不拘。 Table: Courses
Thumbnail
題目敘述 題目會給我們一張Courses資料表,裡面分別有student、class等欄位。其中(student, class) 是這張資料表的複合主鍵Primary key pair。 要求我們,以課程做分群,列出至少有五位同學的課程。 輸出的順序不拘。 Table: Courses
Thumbnail
題目敘述 題目會給我們一個陣列,要求我們返回 兩數之和=target所在的陣列索引值。 題目還額外保證,一定剛好有一組解。 測試範例 Example 1: Input: nums = [2,7,11,15], target = 9 Output: [0,1]
Thumbnail
題目敘述 題目會給我們一個陣列,要求我們返回 兩數之和=target所在的陣列索引值。 題目還額外保證,一定剛好有一組解。 測試範例 Example 1: Input: nums = [2,7,11,15], target = 9 Output: [0,1]
Thumbnail
題目會給定一組已經規定好的介面interface,要求我們實作HashSet這種資料結構。也就是一般數學和程式語言中所說的"集合"。
Thumbnail
題目會給定一組已經規定好的介面interface,要求我們實作HashSet這種資料結構。也就是一般數學和程式語言中所說的"集合"。
Thumbnail
題目會給我們兩條已經從小到大排序好的串列,要求我們依照從小到大的順序,合併這兩條串列。
Thumbnail
題目會給我們兩條已經從小到大排序好的串列,要求我們依照從小到大的順序,合併這兩條串列。
Thumbnail
題目的輸入會給我們一個串列,要求我們從頭到尾反轉整個串列。 例子: 如果輸入是1 -> 2 -> 3,那麼輸出就是 3 -> 2 -> 1
Thumbnail
題目的輸入會給我們一個串列,要求我們從頭到尾反轉整個串列。 例子: 如果輸入是1 -> 2 -> 3,那麼輸出就是 3 -> 2 -> 1
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News