1. 程式人生 > >【LeetCode】207. Course Schedule 解題報告(Python)

【LeetCode】207. Course Schedule 解題報告(Python)

題目描述:

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: 2, [[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: 2, [[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.

Note:

  1. The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input prerequisites.

題目大意

課程表上有一些課,是必須有修學分的先後順序的,必須要求在上完某些課的情況下才能上下一門。問是否有方案修完所有的課程?

解題方法

方法一:拓撲排序,BFS

看到給的第二個測試用例立馬就明白了,就是判斷這些課程能否構成有向無環圖(DAG)。而任何時候判斷DAG的方法要立刻想到拓撲排序。

拓撲排序是對有向無環圖(DAG)而言的,對圖進行拓撲排序即求其中節點的一個拓撲序列,對於所有的有向邊(U, V)(由U指向V),在該序列中節點U都排在節點V之前。

方法是每次選擇入度為0的節點,作為序列的下一個節點,然後移除該節點和以從節點出發的所有邊。

那這個方法比較簡單粗暴了:要迴圈N次,這個迴圈次數並不是遍歷節點的意思,而是我們如果正常取點的話,N次就能把所有的節點都取完了,如果N次操作結束還沒判斷出來,那麼就不是DAG.在這N次中,每次都找一個入度為0的點,並把它的入度變為-1,作為已經取過的點不再使用,同時把從這個點指向的點的入度都-1.

這個過程中,如果找不到入度為0的點,那麼說明存在環。如果N次操作,每次都操作成功的去除了一個入度為0的點,那麼說明這個圖是DAG.

時間複雜度是O(N ^ 2),空間複雜度是O(N)。

class Solution(object):
    def canFinish(self, N, prerequisites):
        """
        :type N,: int
        :type prerequisites: List[List[int]]
        :rtype: bool
        """
        graph = collections.defaultdict(list)
        indegrees = collections.defaultdict(int)
        for u, v in prerequisites:
            graph[v].append(u)
            indegrees[u] += 1
        for i in range(N):
            zeroDegree = False
            for j in range(N):
                if indegrees[j] == 0:
                    zeroDegree = True
                    break
            if not zeroDegree: return False
            indegrees[j] = -1
            for node in graph[j]:
                indegrees[node] -= 1
        return True                

方法二:拓撲排序,DFS

同樣是拓撲排序,但是換了個做法,使用DFS。這個方法是,我們每次找到一個新的點,判斷從這個點出發是否有環。

具體做法是使用一個visited陣列,當visited[i]值為0,說明還沒判斷這個點;當visited[i]值為1,說明當前的迴圈正在判斷這個點;當visited[i]值為2,說明已經判斷過這個點,含義是從這個點往後的所有路徑都沒有環,認為這個點是安全的。

那麼,我們對每個點出發都做這個判斷,檢查這個點出發的所有路徑上是否有環,如果判斷過程中找到了當前的正在判斷的路徑,說明有環;找到了已經判斷正常的點,說明往後都不可能存在環,所以認為當前的節點也是安全的。如果當前點是未知狀態,那麼先把當前點標記成正在訪問狀態,然後找後續的節點,直到找到安全的節點為止。最後如果到達了無路可走的狀態,說明當前節點是安全的。

findOrder函式中的for迴圈是怎麼回事呢?這個和BFS迴圈次數不是同一個概念,這裡的迴圈就是看從第i個節點開始能否到達合理結果。這個節點可能沒有出度了,那就把它直接放到path裡;也可能有出度,那麼就把它後面的節點都進行一次遍歷,如果滿足條件的節點都放到path裡,同時把這次遍歷的所有節點都標記成了已經遍歷;如果一個節點已經被安全的訪問過,那麼就放過它,繼續遍歷下個節點。

時間複雜度是O(N),空間複雜度是O(N)。

class Solution(object):
    def canFinish(self, N, prerequisites):
        """
        :type N,: int
        :type prerequisites: List[List[int]]
        :rtype: bool
        """
        graph = collections.defaultdict(list)
        for u, v in prerequisites:
            graph[u].append(v)
        # 0 = Unknown, 1 = visiting, 2 = visited
        visited = [0] * N
        for i in range(N):
            if not self.dfs(graph, visited, i):
                return False
        return True
        
    # Can we add node i to visited successfully?
    def dfs(self, graph, visited, i):
        if visited[i] == 1: return False
        if visited[i] == 2: return True
        visited[i] = 1
        for j in graph[i]:
            if not self.dfs(graph, visited, j):
                return False
        visited[i] = 2
        return True

參考資料:

日期

2018 年 10 月 6 日 —— 努力看書

相關推薦

no