LeetCode207 課程表
阿新 • • 發佈:2018-12-09
問題:課程表
現在你總共有 n 門課需要選,記為 0
到 n-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。這是不可能的。
說明:
- 輸入的先決條件是由邊緣列表表示的圖形,而不是鄰接矩陣。詳情請參見圖的表示法。
- 你可以假定輸入的先決條件中沒有重複的邊。
提示:
- 這個問題相當於查詢一個迴圈是否存在於有向圖中。如果存在迴圈,則不存在拓撲排序,因此不可能選取所有課程進行學習。
- 通過 DFS 進行拓撲排序 - 一個關於Coursera的精彩視訊教程(21分鐘),介紹拓撲排序的基本概念。
-
拓撲排序也可以通過 BFS 完成。
連結:https://leetcode-cn.com/problems/course-schedule/description/
分析:
1,一門課的先修課可能有多個,只要有一個沒學就沒法學
2,如果進行一輪後,沒有新增學到的課,那麼後續也不會再增加了
3,如果學到的課就是課程數量,則學了所有的,否則學不完。
所以可以設定如下資料結構:
class Class { public: int Num=INT_MAX; //為止 int Status=0; // 未學習 vector<int> dps; };
Num是課程編號,Status是課程狀態,0表示沒學,1表示學了,dps表示以來課程。
可以假設所有的課程都不依賴任何課,初始化完成後,根據依賴關係更新,然後找到不依賴其他課的課程,
以沒有先修課的課程為基礎,持續更新, 直到不再新增學到的課程位置。
更新方式為:如果一個課程的先修課都已經學過了,這門課也可以學習。
AC Code:
class Class { public: int Num=INT_MAX; //為止 int Status=0; // 未學習 vector<int> dps; }; class Solution { public: bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) { bool ret = false; //如果能找到一個起始課程,即不依賴任何課程的,從這個開始學習所有 //如果找不到,則不能進行 vector<Class> classes; for (int i = 0; i < numCourses; i++) { Class tmp; tmp.Num = i; tmp.Status = 0; tmp.dps = vector<int>(); classes.emplace_back(tmp); } int n = prerequisites.size(); sort(prerequisites.begin(), prerequisites.end()); for (int i = 0; i <n; i++) { int tmpnum = prerequisites[i].first; int depnum = prerequisites[i].second; for (int j = 0; j < classes.size(); j++) { if (classes[j].Num == tmpnum) { classes[j].dps.emplace_back(depnum); break; } } } //找到所有的不依賴其他的課程,並且從哪些出發得打所有的不依賴的課程 int learned = 0; vector<int> learnedclass; for (int i = 0; i < classes.size(); i++) { if (classes[i].dps.size() == 0) { classes[i].Status = 1; learned++; learnedclass.emplace_back(classes[i].Num); } } while (true) { int currentlearned = learned; for (int i = 0; i < classes.size(); i++) { if (classes[i].Status == 1) { continue; } else { //這門課沒學,看先修課有沒有學 vector<int> tmpre = classes[i].dps; int learnflag = 1; for (int j = 0; j < tmpre.size(); j++) { if (find(learnedclass.begin(), learnedclass.end(), tmpre[j]) == learnedclass.end()) { learnflag = 0; //有先修課沒學,學不了了 break; } } if (learnflag == 1) { //這門課可以學 classes[i].Status = 1; learned++; learnedclass.emplace_back(classes[i].Num); } } } if (currentlearned == learned) { //沒有增加學習的課程,可以結束了 //if(learned == ) return learned == numCourses; } } return ret; } };
其他:
1.QQ群裡有人提到這個題,周賽第四題沒搞懂dp狀態轉移方程,解決這個問題消磨時間吧。
提示中有提到圖論等知識,不過也就學資料結構的時候學過這個,平時不怎麼用,或者說不知道主動去用,沒用圖論的知識,雖然過了,效率比較低,以後有機會好好補補相關知識。
2.太冷動手,沒太大興趣看了,不過可以優化的地方有,一門課是否能學,看其先修課是否已經學過了,那麼下一次更新的時候,再次檢視就浪費時間,可以看缺了哪些課,這樣待處理的資料會逐漸減少避免重複運算。
3.用時最短code:
1 static const auto _ = []() 2 { 3 ios::sync_with_stdio(false); 4 cin.tie(nullptr); 5 return nullptr; 6 }(); 7 8 class Solution { 9 vector<int> *vex; //vex[a]={b,c}:要學習a,須先學b,c 10 char *visit; //0:從未訪問,1:正在訪問,2:曾經訪問 11 //若成環,返回false 12 bool DFS(int vID){ 13 if(visit[vID]==1) 14 return false; //成環了 15 if(visit[vID]==0){ //0:從未訪問 16 visit[vID]=1; //1:正在訪問 17 for(int i=0;i<vex[vID].size();++i){ 18 if(!DFS(vex[vID][i]))return false; //成環了 19 } 20 visit[vID]=2; //2:曾經訪問 21 } 22 return true; //訪問過了 23 } 24 public: 25 bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) { 26 vex= new vector<int>[numCourses]; 27 visit= new char[numCourses]; 28 fill(visit,visit+numCourses,0); //0:從未訪問 29 for(int i=0;i<prerequisites.size();++i){ 30 auto &edge=prerequisites[i]; 31 vex[edge.first].push_back(edge.second); //[1,0],學習課程 1 之前,你需要完成課程 0。 32 } 33 //逐個訪問 34 for(int i=0;i<numCourses;++i){ 35 if(!DFS(i))return false; 36 } 37 return true; //沒有成環即可 38 } 39 };