1. 程式人生 >實用技巧 >其它 >【力扣】207.課程表

【力扣】207.課程表


題目描述

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

在選修某些課程之前需要一些先修課程。 先修課程按陣列prerequisites 給出,其中prerequisites[i] = [

【力扣】207.課程表

,

【力扣】207.課程表

] ,表示如果要學習課程

【力扣】207.課程表

則 必須 先學習課程

【力扣】207.課程表

。

例如,先修課程對[0, 1] 表示:想要學習課程 0 ,你需要先完成課程 1 。
請你判斷是否可能完成所有課程的學習?如果可以,返回 true ;否則,返回 false 。

來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/course-schedule
著作權歸領釦網路所有。商業轉載請聯絡官方授權,非商業轉載請註明出處。

示例 1:

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

示例 2:

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

分析

關鍵的資訊在“要學習課程

【力扣】207.課程表

則 必須 先學習課程

【力扣】207.課程表

”,
這就意味著課程之間存在一些依賴關係,在示例2中,課程0依賴課程1,課程1依賴課程0,出現了迴圈依賴的情況,因此不能完成學習。看到迴圈依賴我們很容易想到環,進而想到在有向圖中會出現環。於是可以將問題建模為在有向圖中找環。這就有兩種方法,第一種是拓撲排序,存在拓撲排序的圖中一定沒有環。第二種是直接找環,這裡我們選用第二種方法,直接找環。

解題

主要分為2個步驟1.根據prerequisites構建鄰接表 2.查詢環

構建鄰接表時,要注意

圖中的節點個數為numCourses,即課程數。

prerequisites給的是邊資訊,prerequisites[i] = [

【力扣】207.課程表

,

【力扣】207.課程表

]表示存在一條從

【力扣】207.課程表

指向

【力扣】207.課程表

的邊,因此

【力扣】207.課程表

是

【力扣】207.課程表

的一個鄰接節點,其餘以此類推。

查詢環:在對圖的遍歷過程種,對於一個節點,其有三種狀態,0表示還未遍歷,1表示已經遍歷結束(所有鄰接節點都已遍歷),2表示正在遍歷(還有鄰接節點未遍歷),那麼如果在遍歷狀態為1的A節點時,再次遍歷到另一個狀態為1的B節點,就表示兩個節點之間存在環,這並不難理解。我們可以用一個長度為numCourses的int陣列來記錄每個節點的狀態。

具體程式碼如下:

public boolean canFinish(int numCourses, int[][] prerequisites) {
    if (prerequisites.length == 0 || prerequisites.length == 1) return true;

    List<List<Integer>> adj = new ArrayList<>();
    
    int[] visited = new int[numCourses];

    // 新增節點,節點個數為numCourses
    for (int i = 0; i < numCourses; i++) {
        adj.add(new ArrayList<>());
    }
    
    // 新增鄰接節點
    for (int i = 0; i < prerequisites.length; i++) {
        adj.get(prerequisites[i][0]).add(prerequisites[i][1]);
    }

    // 遍歷圖中節點
    for (int i = 0; i < numCourses; i++) {
        if(findCycle(i, visited, adj)) return false;
    }
    return true;
}

public boolean findCycle(int node, int[] visited, List<List<Integer>> adj) {
    // 0表示還未訪問
    // 1表示已訪問過
    // 2表示正在訪問
    if (visited[node] == 2) return true; // 遍歷到某正在訪問的節點,找到環,返回true
    if (visited[node] == 1) return false; // 遍歷到訪問結束的節點,未找到環,返回false
    
    // 標記正在遍歷的節點
    visited[node] = 2;

    // 遍歷鄰接節點
    for (Integer next : adj.get(node)) {
        if(findCycle(next, visited, adj)) return true; //如果找到環,返回true
    }

    visited[node] = 1; // 標記訪問結束的節點
    return false; // 未找到環,返回false
}