leetcode210. 課程表 II / 拓撲排序

現在你總共有 n 門課需要選,記爲 0 到 n-1。

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

給定課程總量以及它們的先決條件,返回你爲了學完所有課程所安排的學習順序。

可能會有多個正確的順序,你只要返回一種就可以了。如果不可能完成所有課程,返回一個空數組。

示例 1:

輸入: 2, [[1,0]] 
輸出: [0,1]
解釋: 總共有 2 門課程。要學習課程 1,你需要先完成課程 0。因此,正確的課程順序爲 [0,1]

示例 2:

輸入: 4, [[1,0],[2,0],[3,1],[3,2]]
輸出: [0,1,2,3] or [0,2,1,3]
解釋: 總共有 4 門課程。要學習課程 3,你應該先完成課程 1 和課程 2。並且課程 1 和課程 2 都應該排在課程 0 之後。
     因此,一個正確的課程順序是 [0,1,2,3] 。另一個正確的排序是 [0,2,1,3]

說明:

  • 輸入的先決條件是由邊緣列表表示的圖形,而不是鄰接矩陣。詳情請參見圖的表示法。
  • 你可以假定輸入的先決條件中沒有重複的邊。

提示:

  • 這個問題相當於查找一個循環是否存在於有向圖中。如果存在循環,則不存在拓撲排序,因此不可能選取所有課程進行學習。
  • 通過 DFS 進行拓撲排序 - 一個關於Coursera的精彩視頻教程(21分鐘),介紹拓撲排序的基本概念。
  • 拓撲排序也可以通過 BFS 完成。

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

基本思想

看到題目,先描述基本思想,畢竟面試的時候手撕代碼前,面試官先讓說一下基本思路。

  • 將輸入的信息存儲爲一個圖,採用圖的鄰接表的形式進行存儲
  • 按照BFS(廣度優先遍歷)
  • 從入度爲0的頂點開始進行遍歷,遍歷的過程中將其鄰接頂點的入度減一,同時將度爲0的頂點入隊,並存入結果中,直到隊列爲空時爲止
  • 如果當前沒有入隊的節點,並且還有未訪問的節點,說明不能完成所有的課程
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> res;
        if(numCourses == 0)
            return res;
        vector<vector<int>> course(numCourses, vector<int>());
        vector<int> coursed(numCourses, 0);
        for(auto p : prerequisites){
            course[p[1]].push_back(p[0]);
            coursed[p[0]]++;
        }
        queue<int> q;
        for(int i = 0; i < numCourses; ++i){
            if(coursed[i] == 0){
                q.push(i);
                res.push_back(i);
            }
        }
        bool flag = true;
        while(!q.empty() && flag){
            int n = q.size();
            flag = false;
            while(n--){
                int t = q.front();
                q.pop();
                for(int i = 0; i < course[t].size(); ++i){
                    int cur = course[t][i];
                    
                    coursed[cur]--;
                    if(coursed[cur] == 0){
                        flag = true;
                        q.push(cur);//注意什麼時候入隊
                        res.push_back(cur);
                        //cout << cur <<"fasd" << endl;
                    }
                    //cout << t << " " << cur << " " << flag << endl;
                }
            }
        }
        if(res.size() == numCourses)
            return res;
        else
            return vector<int>();
    }
};

啊啊啊啊,思想沒有問題,但是寫代碼的時候思緒飄了~~~~
另一種簡潔的寫法:

  • 首先,將入度爲0的節點入隊
  • 當隊列非空的時候循環處理:出隊時,保存到結果中;並訪問結果的鄰接頂點,遞減其鄰接頂點的度,當度爲0的時候入隊
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> res;
        if(numCourses == 0)
            return res;
        vector<vector<int>> course(numCourses, vector<int>());
        vector<int> coursed(numCourses, 0);
        for(auto p : prerequisites){
            course[p[1]].push_back(p[0]);
            coursed[p[0]]++;
        }
        queue<int> q;
        for(int i = 0; i < numCourses; ++i){
            if(coursed[i] == 0){
                q.push(i);
            }
        }
        while(!q.empty()){
            int t = q.front();
            res.push_back(t);
            q.pop();
            for(int i = 0; i < course[t].size(); ++i){
                coursed[course[t][i]]--;
                if(coursed[course[t][i]] == 0)
                    q.push(course[t][i]);
            }
        }
        if(res.size() == numCourses)
            return res;
        else
            return vector<int>();
    }
};

DFS實現拓撲排序:

  • 從圖中一個入度爲0的節點出發,進行深度優先遍歷,如果該節點的鄰接頂點都訪問過了,那麼將該節點入棧;如果該節點已經正在訪問,說明圖中有環
  • 由於該圖不一定是連通圖,依然要處理未曾訪問的節點
  • 遍歷的過程中,標記每個節點的狀態,未曾訪問,正在訪問,已入棧
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> res;//用數組模擬棧
        if(numCourses == 0)
            return res;
        vector<vector<int>> course(numCourses, vector<int>());
        vector<int> coursed(numCourses, 0);
        for(auto p : prerequisites){
            course[p[1]].push_back(p[0]);
            coursed[p[0]]++;
        }
        vector<int> visit(numCourses, 0);//標記每個節點的狀態0:未訪問;1:正在訪問,2:已入結果棧
        for(int i = 0; i < coursed.size(); ++i){
            if(coursed[i] == 0){
                if(dfs(course, i, res, visit))
                    res.push_back(i);
                else
                    return vector<int>();
            }
        }
        if(res.size() == numCourses){
            reverse(res.begin(), res.end());
            return res;
        }
        else
            return vector<int>();
    }
private:
    bool dfs(vector<vector<int>> &course, int p, vector<int> &res, vector<int> &visit){
        if(visit[p] == 2)//說明其周圍的節點已經訪問過了
            return true;
        visit[p] = 1;//當前節點正在訪問
        for(int i = 0; i < course[p].size(); ++i){
            int t = course[p][i];
            if(visit[t] == 0){
                if(dfs(course, course[p][i], res, visit)){
                    res.push_back(course[p][i]);
                    visit[course[p][i]] = 2;
                }
                else
                    return false;
            }
            else if(visit[t] == 1){//說明圖中有環
                return false;
            }        
                
        }
        return true;
    }
};

說明:DFS其實可以在圖中的任意一個未曾訪問的節點出發,不用從入度爲0的頂點出發也可以

class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> res;
        if(numCourses == 0)
            return res;
        vector<vector<int>> course(numCourses, vector<int>());
        for(auto p : prerequisites){
            course[p[1]].push_back(p[0]);
        }
        vector<int> visit(numCourses, 0);//標記每個節點的狀態0:未訪問;1:正在訪問,2:已入結果棧
        for(int i = 0; i < numCourses; ++i){
            if(visit[i] == 0){
                if(!dfs(course, i, res, visit))
                    break;
            }
        }
        if(res.size() == numCourses){
            reverse(res.begin(), res.end());
            return res;
        }
        else
            return vector<int>();
    }
private:
    bool dfs(vector<vector<int>> &course, int p, vector<int> &res, vector<int> &visit){
        //標記該節點正在訪問
        visit[p] = 1;
        for(int i = 0; i < course[p].size(); ++i){
            int t = course[p][i];
            if(visit[t] == 0){
                if(!dfs(course, t, res, visit))
                    return false;
            }
            else if(visit[t] == 1)
                return false;
        }
        res.push_back(p);
        visit[p] = 2;//標記該節點已入棧
        return true;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章