207. Course Schedule 课程表
你這個學(xué)期必須選修 numCourse 門課程,記為 0 到 numCourse-1 。
在選修某些課程之前需要一些先修課程。 例如,想要學(xué)習(xí)課程 0 ,你需要先完成課程 1 ,我們用一個匹配來表示他們:[0,1]
給定課程總量以及它們的先決條件,請你判斷是否可能完成所有課程的學(xué)習(xí)?
示例 1:
輸入: 2, [[1,0]]
輸出: true
解釋: 總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要完成課程 0。所以這是可能的。
示例 2:
輸入: 2, [[1,0],[0,1]]
輸出: false
解釋: 總共有 2 門課程。學(xué)習(xí)課程 1 之前,你需要先完成?課程 0;并且學(xué)習(xí)課程 0 之前,你還應(yīng)先完成課程 1。這是不可能的。
提示:
深度優(yōu)先搜索
給定一個包含 n 個節(jié)點的有向圖 G,給出它的節(jié)點編號的一種排列,如果滿足:**對于圖 G 中的任意一條有向邊 (u, v),u 在排列中都出現(xiàn)在 v 的前面。**那么稱該排列是圖 G 的「拓撲排序」。
將本題建模成一個求拓撲排序的問題:
- 將每一門課看成一個節(jié)點;
- 想要學(xué)習(xí)課程 A 之前必須完成課程 B,那么我們從 B 到 A 連接一條有向邊。這樣以來,在拓撲排序中,B 一定出現(xiàn)在 A 的前面。
將深度優(yōu)先搜索的流程與拓撲排序的求解聯(lián)系起來,用一個棧來存儲所有已經(jīng)搜索完成的節(jié)點。
對于一個節(jié)點 u,如果它的所有相鄰節(jié)點都已經(jīng)搜索完成,那么在搜索回溯到 u 的時候,u 本身也會變成一個已經(jīng)搜索完成的節(jié)點。這里的「相鄰節(jié)點」指的是從 u 出發(fā)通過一條有向邊可以到達的所有節(jié)點。
假設(shè)我們當前搜索到了節(jié)點 u,如果它的所有相鄰節(jié)點都已經(jīng)搜索完成,那么這些節(jié)點都已經(jīng)在棧中了,此時我們就可以把 u 入棧??梢园l(fā)現(xiàn),如果我們從棧頂往棧底的順序看,由于 u 處于棧頂?shù)奈恢?#xff0c;那么 u 出現(xiàn)在所有 u 的相鄰節(jié)點的前面。因此對于 u 這個節(jié)點而言,它是滿足拓撲排序的要求的。
這樣以來,我們對圖進行一遍深度優(yōu)先搜索。當每個節(jié)點進行回溯的時候,我們把該節(jié)點放入棧中。最終從棧頂?shù)綏5椎男蛄芯褪且环N拓撲排序。
對于圖中的任意一個節(jié)點,它在搜索的過程中有三種狀態(tài),即:
- 「未搜索」:我們還沒有搜索到這個節(jié)點;
- 「搜索中」:我們搜索過這個節(jié)點,但還沒有回溯到該節(jié)點,即該節(jié)點還沒有入棧,還有相鄰的節(jié)點沒有搜索完成;
- 「已完成」:我們搜索過并且回溯過這個節(jié)點,即該節(jié)點已經(jīng)入棧,并且所有該節(jié)點的相鄰節(jié)點都出現(xiàn)在棧的更底部的位置,滿足拓撲排序的要求。
通過上述的三種狀態(tài),我們就可以給出使用深度優(yōu)先搜索得到拓撲排序的算法流程,在每一輪的搜索搜索開始時,我們?nèi)稳∫粋€「未搜索」的節(jié)點開始進行深度優(yōu)先搜索。
- 我們將當前搜索的節(jié)點 u 標記為「搜索中」,遍歷該節(jié)點的每一個相鄰節(jié)點 v:
- 如果 v 為「未搜索」,那么我們開始搜索 v,待搜索完成回溯到 u;
- 如果 v 為「搜索中」,那么我們就找到了圖中的一個環(huán),因此是不存在拓撲排序的;
- 如果 v 為「已完成」,那么說明 v 已經(jīng)在棧中了,而 u 還不在棧中,因此 u 無論何時入棧都不會影響到 (u, v) 之前的拓撲關(guān)系,以及不用進行任何操作。
- 當 u 的所有相鄰節(jié)點都為「已完成」時,我們將 u 放入棧中,并將其標記為「已完成」。
在整個深度優(yōu)先搜索的過程結(jié)束后,如果我們沒有找到圖中的環(huán),那么棧中存儲這所有的 nn 個節(jié)點,從棧頂?shù)綏5椎捻樞蚣礊橐环N拓撲排序。
由于我們只需要判斷是否存在一種拓撲排序,而棧的作用僅僅是存放最終的拓撲排序結(jié)果,因此我們可以只記錄每個節(jié)點的狀態(tài),而省去對應(yīng)的棧。
Code
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:def dfs(u: int):nonlocal validvisited[u] = 1for v in edges[u]:if visited[v] == 0:dfs(v)if not valid:returnelif visited[v] == 1:valid = Falsereturnvisited[u] = 2result.append(u)edges = collections.defaultdict(list)visited, result, valid = [0] * numCourses, list(), Truefor info in prerequisites:edges[info[1]].append(info[0])for i in range(numCourses):if valid and not visited[i]:dfs(i)return valid復(fù)雜度分析
時間復(fù)雜度: O(n+m),其中 n 為課程數(shù),m 為先修課程的要求數(shù)。這其實就是對圖進行深度優(yōu)先搜索的時間復(fù)雜度。
空間復(fù)雜度: O(n+m)。題目中是以列表形式給出的先修課程關(guān)系,為了對圖進行深度優(yōu)先搜索,我們需要存儲成鄰接表的形式,空間復(fù)雜度為 O(n+m)。在深度優(yōu)先搜索的過程中,我們需要最多 O(n) 的棧空間(遞歸)進行深度優(yōu)先搜索,因此總空間復(fù)雜度為 O(n+m)。
總結(jié)
以上是生活随笔為你收集整理的207. Course Schedule 课程表的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 互掐!美团“抛弃”支付宝,背后的真相到底
- 下一篇: 337. House Robber II