1. 程式人生 > 實用技巧 >[leetcode/lintcode 題解] Amazon面試題:安排課程

[leetcode/lintcode 題解] Amazon面試題:安排課程

你需要去上n門九章的課才能獲得offer,這些課被標號為 0n-1 有一些課程需要“前置課程”,比如如果你要上課程0,你需要先學課程1,我們用一個匹配來表示他們: [0,1] 給你課程的總數量和一些前置課程的需求,返回你為了學完所有課程所安排的學習順序。 可能會有多個正確的順序,你只要返回一種就可以了。如果不可能完成所有課程,返回一個空陣列。 線上評測地址:領釦題庫官網
例1: 輸入: n = 2, prerequisites = [[1,0]] 輸出: [0,1] 例2: 輸入: n = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] 輸出: [0,1,2,3] or [0,2,1,3]
解題思路 對於兩門課之間的約束關係,很容易聯想到圖,我們可以將課抽象為節點,將約束抽象為一條有向邊,可以用有向圖的相關演算法解決問題。拓撲排序正好可以解決這一問題。 演算法:拓撲排序 一個合法的選課序列就是一個拓撲序,拓撲序是指一個滿足有向圖上,不存在一條邊出節點在入節點後的線性序列,如果有向圖中有環,就不存在拓撲序。可以通過拓撲排序演算法來得到拓撲序,以及判斷是否存在環。 拓撲排序步驟:
  1. 建圖並記錄所有節點的入度。
  2. 將所有入度為0的節點加入佇列。
  3. 取出隊首的元素now,將其加入拓撲序列。
  4. 訪問所有now的鄰接點nxt,將nxt的入度減1,當減到0後,將nxt加入佇列。
  5. 重複步驟34,直到佇列為空。
  6. 如果拓撲序列個數等於節點數,代表該有向圖無環,且存在拓撲序。
複雜度分析 設課程數,即圖的節點數為V。 約束數量,即圖的邊數為E。 時間複雜度O(V + E)
  • 建圖,掃描一遍所有的邊O(E)
  • 每個節點最多入隊出隊1次,複雜度O(V)
  • 鄰接表最終會被遍歷1遍,複雜度O(E)
  • 綜上,總複雜度為O(V + E)
空間複雜度O(V + E)
  • 鄰接表佔用O(V + E)的空間。
  • 佇列最劣情況寫佔用O(V)的空間。
  • 綜上,總複雜度為O(V + E)
public class Solution { /*
* @param numCourses: a total of n courses * @param prerequisites: a list of prerequisite pairs * @return: the course order */ public int[] findOrder(int numCourses, int[][] prerequisites) { List[] graph = new ArrayList[numCourses]; int[] inDegree = new int[numCourses]; for (int i = 0; i < numCourses; i++) { graph[i] = new ArrayList<Integer>(); } // 建圖 for (int[] edge: prerequisites) { graph[edge[1]].add(edge[0]); inDegree[edge[0]]++; } int numChoose = 0; Queue queue = new LinkedList(); int[] topoOrder = new int[numCourses]; // 將入度為 0 的編號加入佇列 for(int i = 0; i < inDegree.length; i++){ if (inDegree[i] == 0) { queue.add(i); } } while (! queue.isEmpty()) { int nowPos = (int)queue.poll(); topoOrder[numChoose] = nowPos; numChoose++; // 將每條邊刪去,如果入度降為 0,再加入佇列 for (int i = 0; i < graph[nowPos].size(); i++) { int nextPos = (int)graph[nowPos].get(i); inDegree[nextPos]--; if (inDegree[nextPos] == 0) { queue.add(nextPos); } } } if (numChoose == numCourses) return topoOrder; return new int[0]; } } 更多題解參考:九章官網Solution