1. 程式人生 > 實用技巧 >LeetCode 207. 課程表 | Python

LeetCode 207. 課程表 | Python

207. 課程表


題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/course-schedule

題目


你這個學期必須選修 numCourse 門課程,記為 0 到 numCourse-1 。

在選修某些課程之前需要一些先修課程。 例如,想要學習課程 0 ,你需要先完成課程 1 ,我們用一個匹配來表示他們:[0,1]

給定課程總量以及它們的先決條件,請你判斷是否可能完成所有課程的學習?

示例 1:

輸入: 2, [[1,0]]
輸出: true
解釋: 總共有 2 門課程。學習課程 1 之前,你需要完成課程 0。所以這是可能的。

示例 2:

輸入: 2, [[1,0],[0,1]]
輸出: false
解釋: 總共有 2 門課程。學習課程 1 之前,你需要先完成​課程 0;並且學習課程 0 之前,你還應先完成課程 1。這是不可能的。

提示:

  1. 輸入的先決條件是由 邊緣列表 表示的圖形,而不是 鄰接矩陣 。詳情請參見圖的表示法
  2. 你可以假定輸入的先決條件中沒有重複的邊。
  3. 1 <= numCourses <= 10^5

解題思路


思路:拓撲排序(BFS,DFS)

其實,這是一道經典的【拓撲排序】問題。

首先先審題,結合示例 1 和示例 2,我們其實可以看到,其實題目問的是給定輸入先決條件表示的圖形(也就是課程表)是否是有向無環圖。也是說,當確定先決條件之後,圖形不能存在環,否則不成立。

有向無環圖(DAG):指的一個有向圖從任意頂點出發無法經過若干條邊回到該點,則該圖是一個有向無環圖。

有向無環圖,詳細資訊可由下方入口進行了解

https://en.wikipedia.org/wiki/Directed_acyclic_graph (維基百科:有向無環圖)
https://baike.baidu.com/item/%E6%9C%89%E5%90%91%E6%97%A0%E7%8E%AF%E5%9B%BE/10972513?fr=aladdin (百度百科:有向無環圖)

那麼當要求課程安排圖是否是有向無環圖時,我們用拓撲排序來判斷。拓撲排序:將圖所有頂點進行排序成線性序列,使得圖中任意一個有向邊 uv,u 線上性序列中出現在 v 前面。

拓撲排序,詳細資訊可由下方入口進行了解
https://en.wikipedia.org/wiki/Topological_sorting

(維基百科:拓撲排序)
https://baike.baidu.com/item/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F/5223807?fr=aladdin (百度百科:拓撲排序)

在題目中的提示中有說明,輸入的先決條件時由 邊緣列表 表示,在這裡,我們要將此轉換為 鄰接表

鄰接表,詳細資訊可由下方入口進行了解
https://en.wikipedia.org/wiki/Adjacency_list (維基百科:鄰接表)
https://baike.baidu.com/item/%E9%82%BB%E6%8E%A5%E8%A1%A8/9796152?fr=aladdin (百度百科:鄰接表)

廣度優先搜尋(BFS)

首先,先將邊緣列表表示的先決條件轉換得到鄰接表。然後統計圖中每個節點的入度情況,儲存在一個列表中。(當入度為 0 時,表示點不作為任何邊的終點,也就說該點是所有連線邊的起點)

在這裡,定義輔助佇列 queue,將所有入度為 0 的節點存入佇列中,用於後續處理。

開始進行搜尋,令佇列中的節點出隊:

  • 將當前節點鄰接節點入度減 1;
  • 當鄰接節點入度為 0 時,將其入隊。

當佇列中的節點出隊時,令課程總量減 1:

  • 如果課程圖是有向無環圖,若完成拓撲排序,那麼所有的節點都會入隊出隊;
  • 若是課程圖存在環,那麼一定存在節點入度不為 0 的情況;
  • 也就是說,當所有節點入隊出隊後,課程總量為 0 的情況下,也就能證明課程圖是否是有向無環圖。

具體的程式碼見【程式碼實現 # 廣度優先搜尋】

深度優先搜尋(DFS)

我們先看使用深度優先搜尋的思路來判斷圖是否有環。

在這裡,我們藉助一個輔助列表(當然也可以考慮用棧)標記每個節點的狀態:

  • 未標記,令此時狀態為 0;
  • 臨時標記,令此時狀態為 1;
  • 永久標記,令此時狀態為 -1。

開始對每個節點進行深度優先搜尋,當存在環時,返回 False,執行過程如下:

  • 當節點狀態為 -1 時,表示已永久標記,此時已搜尋完畢,不許重複搜尋,直接返回 True;
  • 當節點狀態為 1 時,表示臨時標記,也就說此節點,在此次深搜中又一次對該節點進行了搜尋,那麼表示圖中存在環,那麼直接返回 False;
  • 當狀態為 0 時,先將節點臨時標記(令狀態為 1),然後以此點繼續搜尋,直至遇到終止條件。
  • 當節點搜尋完畢,未發現閉環,則去除臨時標記,將節點進行永久標記(令狀態為 -1)

如果所有節點都搜尋完畢後,不存在環,則返回 True。

具體的程式碼見【程式碼實現 # 深度優先搜尋】

程式碼實現


# 廣度優先搜尋
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        from collections import deque

        # 將邊緣列表表示的先決條件轉化為 鄰接表
        adjacency = [[] for _ in range(numCourses)]
        # 定義列表統計圖中每個節點的入度情況
        indegree = [0] * numCourses

        for info in prerequisites:
            adjacency[info[1]].append(info[0])
            indegree[info[0]] += 1
        
        queue = deque()

        # 將入度為 0 的節點入隊
        for i in range(len(indegree)):
            if not indegree[i]:
                queue.append(i)
        # 開始進行搜尋
        while queue:
            u = queue.popleft()
            numCourses -= 1
            # 搜尋鄰接節點
            for v in adjacency[u]:
                # 將鄰接節點入度減 1
                indegree[v] -= 1
                # 如果入度為 0,入隊
                if indegree[v] == 0:
                    queue.append(v)

        return numCourses == 0

# 深度優先搜尋
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        def dfs(adjacency, sign, i):
            if sign[i] == -1:
                return True
            if sign[i] == 1:
                return False
            # 開始搜尋,先進行臨時標記
            sign[i] = 1
            # 以此節點往下搜尋
            for j in adjacency[i]:
                if not dfs(adjacency, sign, j):
                    return False
            sign[i] = -1
            return True

        # 定義輔助列表標記狀態,初始化為 0,表示未標記
        sign = [0] * numCourses
        # 將邊緣列表表示的先決條件轉化為 鄰接表
        adjacency = [[] for _ in range(numCourses)]
        for info in  prerequisites:
            adjacency[info[1]].append(info[0])
        
        # 開始深搜
        for i in range(numCourses):
            if not dfs(adjacency, sign, i):
                return False
        return True

實現結果


歡迎關注


公眾號 【書所集錄