Question
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, return the ordering of courses you should take to finish all courses.
There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.
Example 1:
Input: 2, [[1,0]]
Output: [0,1]
Explanation: There are a total of 2 courses to take. To take course 1 you should have finished
course 0. So the correct course order is [0,1] .
Example 2:
Input: 4, [[1,0],[2,0],[3,1],[3,2]]
Output: [0,1,2,3] or [0,2,1,3]
Explanation: There are a total of 4 courses to take. To take course 3 you should have finished both
courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0.
So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3] .
Note:
The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.You may assume that there are no duplicate edges in the input prerequisites.
This question asks for an order in which prerequisite courses must be taken first. This prerequisite relationship reminds one of directed graphs. Then, the problem reduces to find a topological sort order of the courses, which would be a DAG if it has a valid order.
BFS & DFS Solution
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] incLinkCounts = new int[numCourses];
List<List<Integer>> adjs = new ArrayList<>(numCourses);
initialiseGraph(incLinkCounts, adjs, prerequisites);
//return solveByBFS(incLinkCounts, adjs);
return solveByDFS(adjs);
}
//The first step is to transform it into a directed graph.
//Since it is likely to be sparse,we use adjacency list graph data structure.
// 1 -> 2 means 1 must be taken before 2.
private void initialiseGraph(int[] incLinkCounts, List<List<Integer>> adjs, int[][] prerequisites){
int n = incLinkCounts.length;
while (n-- > 0) adjs.add(new ArrayList<>());
for (int[] edge : prerequisites) {
incLinkCounts[edge[0]]++;
adjs.get(edge[1]).add(edge[0]);
}
}
//How can we obtain a topological sort order of a DAG?
//We observe that if a node has incoming edges, it has prerequisites.
//Therefore, the first few in the order must be those with no prerequisites, i.e. no incoming edges.
// Any non-empty DAG must have at least one node without incoming links.
//You can draw a small graph to convince yourself.
//If we visit these few and remove all edges attached to them,
// we are left with a smaller DAG, which is the same problem. This will then give our BFS solution.
private int[] solveByBFS(int[] incLinkCounts, List<List<Integer>> adjs){
int[] order = new int[incLinkCounts.length];
Queue<Integer> toVisit = new ArrayDeque<>();
for (int i = 0; i < incLinkCounts.length; i++) {
if (incLinkCounts[i] == 0) toVisit.offer(i);
}
int visited = 0;
while (!toVisit.isEmpty()) {
int from = toVisit.poll();
order[visited++] = from;
for (int to : adjs.get(from)) {
incLinkCounts[to]--;
if (incLinkCounts[to] == 0) toVisit.offer(to);
}
}
return visited == incLinkCounts.length ? order : new int[0];
}
//Another way to think about it is the last few in the order must be those
//which are not prerequisites of other courses. Thinking it recursively means
//if one node has unvisited child node, you should visit them first before
// you put this node down in the final order array. This sounds like
//the post-order of a DFS. Since we are putting nodes down
//in the reverse order, we should reverse it back to correct ordering or use a stack.
private int[] solveByDFS(List<List<Integer>> adjs) {
BitSet hasCycle = new BitSet(1);
BitSet visited = new BitSet(adjs.size());
BitSet onStack = new BitSet(adjs.size());
Deque<Integer> order = new ArrayDeque<>();
for (int i = adjs.size() - 1; i >= 0; i--) {
if (visited.get(i) == false && hasOrder(i, adjs, visited, onStack, order) == false)
return new int[0];
}
int[] orderArray = new int[adjs.size()];
for (int i = 0; !order.isEmpty(); i++) orderArray[i] = order.pop();
return orderArray;
}
private boolean hasOrder(int from, List<List<Integer>> adjs, BitSet visited, BitSet onStack, Deque<Integer> order) {
visited.set(from);
onStack.set(from);
for (int to : adjs.get(from)) {
if (visited.get(to) == false) {
if (hasOrder(to, adjs, visited, onStack, order) == false)
return false;
} else if (onStack.get(to) == true) {
return false;
}
}
onStack.clear(from);
order.push(from);
return true;
}
Topological Sort based on DFS with a bit OO
private int N = 0;
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] result = new int[numCourses];
Course[] courses = new Course[numCourses];
for (int i = 0; i < numCourses; i++) {
courses[i] = new Course(i);
}
for (int i = 0; i < prerequisites.length; i++) {
courses[prerequisites[i][0]].add(courses[prerequisites[i][1]]);
}
for (int i = 0; i < numCourses; i++) {
if (isCyclic(courses[i], result)) {
return new int[0];
}
}
return result;
}
private boolean isCyclic(Course cur, int[] result) {
if (cur.tested == true) return false;
if (cur.visited == true) return true;
cur.visited = true;
for (Course c : cur.pre) {
if (isCyclic(c, result)) {
return true;
}
}
cur.tested = true;
result[N++] = cur.number;
return false;
}
class Course {
boolean visited = false;
boolean tested = false;
int number;
List<Course> pre = new ArrayList<Course>();
public Course(int i) {
number = i;
}
public void add(Course c) {
pre.add(c);
}
}
share my DFS solution with clear comments
public class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
//prepare
List<List<Integer>> graph = new ArrayList<>();
for(int i = 0; i < numCourses; i++){
graph.add(new ArrayList<>());
}
for(int[] pair : prerequisites){
int prev = pair[1];
int next = pair[0];
graph.get(prev).add(next);
}
Map<Integer, Integer> visited = new HashMap<>();
//initail visited
for(int i = 0; i < numCourses; i++){
visited.put(i, 0);//0 -> unvisited, 1 -> visiting, 2 -> visited
}
List<Integer> res = new ArrayList<>();
for(int i = 0; i < numCourses; i++){
if(!topoSort(res, graph, visited, i)) return new int[0];
}
int[] result = new int[numCourses];
for(int i = 0; i < numCourses; i++){
result[i] = res.get(numCourses - i - 1);
}
return result;
}
//the return value of this function only contains the ifCycle info and does not interfere dfs process. if there is Cycle, then return false
private boolean topoSort(List<Integer> res, List<List<Integer>> graph, Map<Integer, Integer> visited, int i){
int visit = visited.get(i);
if(visit == 2){//when visit = 2, which means the subtree whose root is i has been dfs traversed and all the nodes in subtree has been put in the result(if we request), so we do not need to traverse it again
return true;
}if(visit == 1){
return false;
}
visited.put(i, 1);
for(int j : graph.get(i)){
if(!topoSort(res, graph, visited, j)) return false;
}
visited.put(i, 2);
res.add(i);//the only difference with traversing a graph
return true;
}
}