[LintCode] Course Schedule
There are a total of n courses you have to take, labeled from 0
to n - 1
.
Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]
Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?
Example
Given n = 2
, prerequisites = [[1,0]]
Return true
Given n = 2
, prerequisites = [[1,0],[0,1]]
Return false
Solution 1. DFS, O(n + m) runtime
Algorithm: Detect if there is any cycle in the given graph. Keep track of vertices currently in recursion stack of method for dfs traversal. If we reach a vertext that is already in the recursion stack, it means we
have a cycle. If a directed graph has a cycle, then it has no topological ordering.
This DFS solutions does not work well on large input as the depth of the recursive method stack will get pretty big.
version 1.
1 public class Solution { 2 public boolean canFinish(int numCourses, int[][] prerequisites) { 3 if(prerequisites == null || prerequisites.length == 0) 4 { 5 return true; 6 } 7 ArrayList<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>(); 8 for(int i = 0; i < numCourses; i++) 9 { 10 graph.add(new ArrayList<Integer>()); 11 } 12 for(int j = 0; j < prerequisites.length; j++) 13 { 14 graph.get(prerequisites[j][1]).add(prerequisites[j][0]); 15 } 16 boolean[] visited = new boolean[numCourses]; 17 boolean[] recursionStack = new boolean[numCourses]; 18 for(int i = 0; i < numCourses; i++) 19 { 20 visited[i] = false; 21 recursionStack[i] = false; 22 } 23 boolean ifHasCycle = false; 24 for(int node = 0; node < numCourses; node++) 25 { 26 if(visited[node] == false && hasCycle(node, visited, recursionStack, graph)) 27 { 28 ifHasCycle = true; 29 break; 30 } 31 } 32 if(ifHasCycle) 33 { 34 return false; 35 } 36 return true; 37 } 38 private boolean hasCycle(int node, boolean[] visited, boolean[] recursionStack, 39 ArrayList<ArrayList<Integer>> graph) 40 { 41 if(visited[node] == false) 42 { 43 visited[node] = true; 44 recursionStack[node] = true; 45 46 for(Integer neighbor : graph.get(node)) 47 { 48 if(visited[neighbor] == false && hasCycle(neighbor, visited, recursionStack, graph)) 49 { 50 return true; 51 } 52 else if(recursionStack[neighbor] == true) 53 { 54 return true; 55 } 56 } 57 } 58 recursionStack[node] = false; 59 return false; 60 } 61 }
version 2.
1. construct a list of integer lists that represent a directed graph.
2. create two hash sets: a gray set that contains vertices that are being explored; a black set that contains vertices that have been completely explored.
3. for each vertices, do a dfs and check if there is a directed cycle.
The dfs algorithm:
1. add the current vertex to the gray set, indicating that this current vertex is being explored.
2. for each of its neighbors of this current vertex:
a. if it is already in the black set, skip;
b. if it is already in the gray set, return true to indicate there is a cycle;
c. otherwise, recursively call dfs on this neigbhor; if this recursive call returns true, return true;
3. after exploring all neighbors, and there is no cycle, remove the current vertex from the gray set and add it to the black set, then return false to indicate there is no cycle involving the current vertex.
1 public boolean canFinish(int numCourses, int[][] prerequisites) { 2 if(prerequisites == null || prerequisites.length == 0) 3 { 4 return true; 5 } 6 ArrayList<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>(); 7 for(int i = 0; i < numCourses; i++) 8 { 9 graph.add(new ArrayList<Integer>()); 10 } 11 for(int j = 0; j < prerequisites.length; j++) 12 { 13 graph.get(prerequisites[j][1]).add(prerequisites[j][0]); 14 } 15 boolean isCycle = false; 16 Set<Integer> graySet = new HashSet<Integer>(); 17 Set<Integer> blackSet = new HashSet<Integer>(); 18 for(int i = 0; i < numCourses; i++) { 19 if(dfs(i, graySet, blackSet, graph)) { 20 isCycle = true; 21 break; 22 } 23 } 24 return !isCycle; 25 } 26 private boolean dfs(int current, Set<Integer> graySet, Set<Integer> blackSet, 27 ArrayList<ArrayList<Integer>> graph) { 28 graySet.add(current); 29 for(Integer neighbor : graph.get(current)) { 30 if(blackSet.contains(neighbor)) { 31 continue; 32 } 33 if(graySet.contains(neighbor)) { 34 return true; 35 } 36 if(dfs(neighbor, graySet, blackSet, graph)) { 37 return true; 38 } 39 } 40 graySet.remove(current); 41 blackSet.add(current); 42 return false; 43 }
Solution 2. BFS, O(n + m) runtime, O(n + m) space
Key idea: the starting courses must not have any dependent courses, meaning they must not have any incoming edges. When adding courses in topological order, only courses that only have incoming edges from those that have already been added to the final result should be added.
Algorithm:
1. Construct a list of integer lists as the adjacency list representation of the given graph.
2. Construct a count array that stores the number of incoming edges for each vertex.
3. Add all vertices whose incoming edges are 0 to a queue and start a bfs traversal. Keep a count of how many vertices have been explored.
4. Each time a vertex is dequeued, conceptually cut this vertex and its outgoing edges from the graph. To do this, update its neighbors' incoming edges numbers by deducting 1. Add any neighbors whose incoming edges number becomes 0 to the queue.
5. Repeat step 4 until the queue is empty.
6. Check if the count is the same with the total number of vertices; If not, it means there is at least one directed cycle in the constructed graph. The vertices that are not explored are all the vertices in cycles.
1 public boolean canFinish(int numCourses, int[][] prerequisites) { 2 ArrayList<ArrayList<Integer>> edges = new ArrayList<ArrayList<Integer>>(); 3 int[] incomingEdgeNum = new int[numCourses]; 4 5 for(int i = 0; i < numCourses; i++) 6 { 7 edges.add(new ArrayList<Integer>()); 8 } 9 int courseNum; 10 for(int i = 0; i < prerequisites.length; i++) 11 { 12 courseNum = prerequisites[i][0]; 13 incomingEdgeNum[courseNum]++; 14 edges.get(prerequisites[i][1]).add(prerequisites[i][0]); 15 } 16 17 Queue<Integer> queue = new LinkedList<Integer>(); 18 for(int i = 0; i < incomingEdgeNum.length; i++) 19 { 20 if(incomingEdgeNum[i] == 0) 21 { 22 queue.add(i); 23 } 24 } 25 26 int count = 0; 27 while(!queue.isEmpty()) 28 { 29 int course = queue.poll(); 30 count++; 31 int n = edges.get(course).size(); 32 for(int i = 0; i < n; i++) 33 { 34 int pointer = edges.get(course).get(i); 35 incomingEdgeNum[pointer]--; 36 if(incomingEdgeNum[pointer] == 0) 37 { 38 queue.add(pointer); 39 } 40 } 41 } 42 if(count < numCourses) 43 { 44 return false; 45 } 46 return true; 47 }
Related Problems
Course Schedule II
Topological Sorting