圖論應用經典 課程表 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
90會員
425內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
題目敘述 題目會給定我們一顆二元搜索樹的根結點root,和任意兩個樹中的節點p和q。 要求我們找出p, q最靠近的公共祖先節點。 題目的原文敘述 測試範例 Example 1: Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q
題目會給我們一個pair陣列,裡面都是原本陣列相鄰元素形成的pair,順序已經被打散。 要求我們從pair陣列重建出原本的陣列。 答案可能有不只一組,任選一組回傳即可。
題目會給一顆二元樹,要求我們計算節點值 和 子樹平均值相等的node有幾個。
題目會給我們節點總數目n、左子樹的關係陣列、右子樹的關係陣列,要求我們驗證在給定的條件下,能不能構成一顆合法的二元樹。
題目會給定兩個參數,一個是底數x,一個是次方n。要求我們計算出x^n。 要求實作myPow(self, x: float, n: int) -> float 函數的內部邏輯。 也就是說,不可以呼叫程式語言內建計算指數的library
題目的輸入會給我們一個串列,要求我們從頭到尾反轉整個串列。 例子: 如果輸入是1 -> 2 -> 3,那麼輸出就是 3 -> 2 -> 1
題目敘述 題目會給定我們一顆二元搜索樹的根結點root,和任意兩個樹中的節點p和q。 要求我們找出p, q最靠近的公共祖先節點。 題目的原文敘述 測試範例 Example 1: Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q
題目會給我們一個pair陣列,裡面都是原本陣列相鄰元素形成的pair,順序已經被打散。 要求我們從pair陣列重建出原本的陣列。 答案可能有不只一組,任選一組回傳即可。
題目會給一顆二元樹,要求我們計算節點值 和 子樹平均值相等的node有幾個。
題目會給我們節點總數目n、左子樹的關係陣列、右子樹的關係陣列,要求我們驗證在給定的條件下,能不能構成一顆合法的二元樹。
題目會給定兩個參數,一個是底數x,一個是次方n。要求我們計算出x^n。 要求實作myPow(self, x: float, n: int) -> float 函數的內部邏輯。 也就是說,不可以呼叫程式語言內建計算指數的library
題目的輸入會給我們一個串列,要求我們從頭到尾反轉整個串列。 例子: 如果輸入是1 -> 2 -> 3,那麼輸出就是 3 -> 2 -> 1
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
童軍教育素養導向課程之教學設計與實務分享,包含課程設計原則、分級制度、班級經營、跨領域教學、議題融入、評量方式、教學科技應用與教師專業發展等面向。強調以學生為中心,關注學生學習動機與挫折應變能力,並善用科技工具提升教學成效。
本課程將教授占卜牌陣的基本知識和技能,並透過互動式教學方式,期望學生能以此了解占卜的過程。課程中著重於教導學生直指核心、吉普賽十字、處境馬蹄三者牌陣的解讀,並透過實際解讀練習來提升學生的技能。除此之外,課程還將教授問題的修改技巧,讓學生能夠提出開放式問句,並以互動式的方式解讀牌陣。
本文章為塔羅牌陣教學課程資訊,包含教學目標、技能、情意、學習內容、學習歷程、學習環境、學習評量、教學計劃等。課程將以各種牌陣的解釋、教學示範、小考與作業等方式進行。
Thumbnail
這堂課程將解密商學院科系差異以及共同的必修科目,以議題角度帶你深入淺出的認識商學院必修科目:經濟學、統計學、會計學。透過這門課程,讓你瞭解金融業的熱門議題及商學院的校系課程內容及差異,幫助你探索大學科系並規劃未來,並提供自主學習計畫表參考範本。
Thumbnail
上課日期: 7月份:7/12.7/26 8月份:8/9.8/23.8/30 上課時間: 20:00~22:00每次兩小時,共10小時 〈會依實際學生進度增加課程〉 團體課程最多6人,因多人可能無法依依解答你的問題 如想要老師專注的教導你,請跟老師預約1對1的私人課程 上課
參加課程的動機 我從未認真想過有沒有 SOP 可以拆解問題、分析問題,建立一套框架,有助於達到想要的目標。課堂前的自我準備、課堂中與人互動我相信是這堂課亮點之一,這將顛覆傳統大學課堂被動吸收與發想的印象。(結果好像遠遠不只 SOP,而是大腦都重整了!) 課程組成 首先,學生與引導師(facil
Thumbnail
新的學年即將開始,學校的課表出爐了!但這些課程的名字和實際內容之間到底有什麼樣的關聯呢?透過這篇文章,讓我們一起揭開課表背後的祕密。
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
童軍教育素養導向課程之教學設計與實務分享,包含課程設計原則、分級制度、班級經營、跨領域教學、議題融入、評量方式、教學科技應用與教師專業發展等面向。強調以學生為中心,關注學生學習動機與挫折應變能力,並善用科技工具提升教學成效。
本課程將教授占卜牌陣的基本知識和技能,並透過互動式教學方式,期望學生能以此了解占卜的過程。課程中著重於教導學生直指核心、吉普賽十字、處境馬蹄三者牌陣的解讀,並透過實際解讀練習來提升學生的技能。除此之外,課程還將教授問題的修改技巧,讓學生能夠提出開放式問句,並以互動式的方式解讀牌陣。
本文章為塔羅牌陣教學課程資訊,包含教學目標、技能、情意、學習內容、學習歷程、學習環境、學習評量、教學計劃等。課程將以各種牌陣的解釋、教學示範、小考與作業等方式進行。
Thumbnail
這堂課程將解密商學院科系差異以及共同的必修科目,以議題角度帶你深入淺出的認識商學院必修科目:經濟學、統計學、會計學。透過這門課程,讓你瞭解金融業的熱門議題及商學院的校系課程內容及差異,幫助你探索大學科系並規劃未來,並提供自主學習計畫表參考範本。
Thumbnail
上課日期: 7月份:7/12.7/26 8月份:8/9.8/23.8/30 上課時間: 20:00~22:00每次兩小時,共10小時 〈會依實際學生進度增加課程〉 團體課程最多6人,因多人可能無法依依解答你的問題 如想要老師專注的教導你,請跟老師預約1對1的私人課程 上課
參加課程的動機 我從未認真想過有沒有 SOP 可以拆解問題、分析問題,建立一套框架,有助於達到想要的目標。課堂前的自我準備、課堂中與人互動我相信是這堂課亮點之一,這將顛覆傳統大學課堂被動吸收與發想的印象。(結果好像遠遠不只 SOP,而是大腦都重整了!) 課程組成 首先,學生與引導師(facil
Thumbnail
新的學年即將開始,學校的課表出爐了!但這些課程的名字和實際內容之間到底有什麼樣的關聯呢?透過這篇文章,讓我們一起揭開課表背後的祕密。