Leetcode上有這樣一道題:有代號0,1,2……n-1的n門課程。其中選擇某些課程需要另一些課程作爲前提條件。用一組pair來表示這些條件:[1,0],[1,2],表示如果要選修課程1,必須先選修課程0和課程2。問是否可能把這n門課程都選修一遍。
這個問題看起來相當複雜。難點在於,一門課程可能需要多門課程作爲前提條件,這樣就很難找到一條可以不重複地遍歷所有課程的方法。
比較笨的方法就是先找出那些不需要前提條件的課程,然後由他們出發將那些只需要他們作爲前提條件的課程修完。再循環往復。一直到某一次遍歷之後,並沒有修到新的課程。最後再檢查是否還有課程沒能修夠。這樣的話,最壞情況,我們是時間複雜度最壞能達到O(N^3)。使用了unordered_multimap作爲輔助工具,代碼被Accept了。但是運行時間是最好的coder的10倍。於是,細細研究他人的算法。
2 BFS
其實。這道題難在用一維數據結構來表示這些前提條件,很容易增大時間複雜度。於是,考慮用二維數據結構來表示。
圖!而題中給的條件明顯是一對多的。那麼,適合用鄰接鏈表法表示。採用數據結構vector<unordered_set<int>> map來表示。vector的每個元素表示由某門課程作爲前提條件的一組課程。每門課程需要的前提條件可以用一組向量表示vector<int> de。我們用入度稱呼這個值,也就是說在圖中,進入該節點有多少條路徑。那麼對於那些不需要前提條件就完成的課程,其入度爲0。
BFS法,廣度優先。我們先找到一門入度爲0的課,然後由它作爲前提條件的課程的入度都可以減1,因爲這門課程學到了,就等於不需要這個前提條件了。而以它爲前提的課程就是該節點的鄰接節點。用這種方法,我們每次學到一門課程,N次就可以學完所有的課程,如果某一次沒有學到新的課程,那麼最終肯定是無法學完的。
上代碼:
class Solution { public: bool canFinish(int n, vector<pair<int, int>>& pre) { vector<unordered_set<int> > gra(n); vector<int> de(n,0); //make graph for(auto a:pre) { gra[a.second].insert(a.first); } //calculate the indegree for(int i=0;i<n;++i) { for(auto a:gra[i]) de[a]++; } for(int i=0;i<n;++i) { int j=0; for(;j<n;++j) { if(de[j]==0) break; } if(j==n) return 0; //this time could not learn a new lesson and we won't make it de[j]=-1; //in case next time we will learn this lesson again for(auto a:gra[j]) de[a]--; //any lesson take this lesson as it's prerequisites, it's indegree decrease 1 } return 1; } };
3 DFS
那麼DFS(深度優先搜索)是怎麼樣的思路呢?其實,如果這個圖中存在一個環的話,那麼肯定無法完成任務。那麼什麼情況下會完不成任務呢?也就是說1->2,2->3,3->1,也就是說形成了一個環。所以我們把原問題轉化爲檢測圖中是否有環。檢測環的方法?從某個節點出發,如果又回到該節點,那麼此圖有環!
我們依次從任意節點出發,遍歷該節點可以到達的所有節點,我們用一個向量vector<bool> onePath來記錄從該節點出發經過的節點。爲了防止重複,我們用變量vector<bool> path記錄所有遍歷過的節點。
上代碼:
class Solution { public: bool canFinish(int n, vector<pair<int, int>>& pre) { vector<unordered_set<int> > gra(n); vector<bool> path(n,0),onePath(n,0); //make graph for(auto a:pre) { gra[a.second].insert(a.first); } for(int i=0;i<n;++i) { if(path[i]) continue; if(dfs_circle(gra,i,path,onePath)) return 0; } return 1; } bool dfs_circle(vector<unordered_set<int> >& gra,int node,vector<bool>& path,vector<bool>& onePath) { if(onePath[node]==1) return true; path[node]=onePath[node]=1; for(auto a:gra[node]) if(dfs_circle(gra,a,path,onePath)) return 1; onePath[node]=0; return 0; } };
這種轉化問題的解法真得很巧妙!太贊!
另外,對於很多條件有交叉的題目,就考慮用圖論方法解決,尤其是DFS和BFS算法。