This is No.210 in LeetCode.
I can’t solve this problem by self, but I found a real solid solution and explanation.
This is our "main" function:
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] incLinkCounts = new int[numCourses]; // incoming edges
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) { // check each [a,b]
incLinkCounts[edge[0]]++; // incrment if edge[0] has prerequisite
adjs.get(edge[1]).add(edge[0]); // adjs: edge[1] -> 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. 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++) { // go over incoming array
if (incLinkCounts[i] == 0) toVisit.offer(i); // if incoming is 0 ( which means entry-level class, add it to queue)
}
int visited = 0;
while (!toVisit.isEmpty()) { // if queue not empty
int from = toVisit.poll(); // from = class from queue
order[visited++] = from; // add class to queue and increment index
for (int to : adjs.get(from)) { // iterator each 'to' class of from
incLinkCounts[to]--; // decrement this incoming number
if (incLinkCounts[to] == 0) toVisit.offer(to); // if this 'to' class become new 'entry-level class
}
}
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;
}