package com.freetymekiyan.algorithms.level.medium; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.BitSet; import java.util.Deque; import java.util.List; import java.util.Queue; /** * There are a total of n courses you have to take, labeled from 0 to n - 1. * <p> * 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] * <p> * Given the total number of courses and a list of prerequisite pairs, return the ordering of courses you should take * to finish all courses. * <p> * 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. * <p> * For example: * <p> * 2, [[1,0]] * 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] * <p> * 4, [[1,0],[2,0],[3,1],[3,2]] * 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]. * <p> * Note: * The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a * graph is represented. * <p> * Hints: * 1. This problem is equivalent to finding the topological order in a directed graph. If a cycle exists, no * topological ordering exists and therefore it will be impossible to take all courses. * 2. Topological Sort via DFS - A great video tutorial (21 minutes) on Coursera explaining the basic concepts of * Topological Sort. * 3. Topological sort could also be done via BFS. * <p> * Company Tags: Facebook, Zenefits * Tags: Depth-first Search, Breadth-first Search, Graph, Topological Sort * Similar Problems: (M) Course Schedule, (H) Alien Dictionary, (M) Minimum Height Trees */ public class CourseSchedule2 { /** * Topological Sort. * Build graph first, then do dfs or bfs. */ public int[] findOrder(int numCourses, int[][] prerequisites) { int[] inDegrees = new int[numCourses]; List<List<Integer>> adjacent = new ArrayList<>(numCourses); initGraph(inDegrees, adjacent, prerequisites); // return dfs(numCourses, adjacent); return bfs(inDegrees, adjacent); } /** * Build an adjacency list and an incoming degree array of graph. * Don't know if its acyclic yet. * Have to check later in topological sort. * * @param indegrees In-degree of each node. */ private void initGraph(int[] indegrees, List<List<Integer>> adjs, int[][] prerequisites) { int n = indegrees.length; while (n-- > 0) { adjs.add(new ArrayList<>()); } for (int[] edge : prerequisites) { indegrees[edge[0]]++; adjs.get(edge[1]).add(edge[0]); } } /** * Topological Sort. BFS. * Start from all nodes with 0 in-degree, which means no prerequisites. * While queue is not empty: * | Dequeue the next node, add it to result. * | Then remove it from the graph by reducing the in-degree of its adjacent nodes. * | If adjacent node's in-degree becomes 0, add it to queue. * Finally, check whether all nodes are visited. */ private int[] bfs(int[] inDegrees, List<List<Integer>> adjs) { int[] order = new int[inDegrees.length]; Queue<Integer> queue = new ArrayDeque<>(); for (int i = 0; i < inDegrees.length; i++) { if (inDegrees[i] == 0) { // Add all 0 in-degree node to queue first. queue.offer(i); } } int i = 0; // An index to result array. while (!queue.isEmpty()) { int from = queue.poll(); order[i++] = from; // Add it to result and update index. for (int to : adjs.get(from)) { // Neighbors. inDegrees[to]--; if (inDegrees[to] == 0) { // If becomes 0, add to queue. queue.offer(to); } } } // IMPORTANT! Check whether all vertices are added to result. // Otherwise, the graph is not DAG. return i == inDegrees.length ? order : new int[0]; } /** * Topological Sort. DFS. * Create a boolean array from visited state of each node. * Create a stack to store the result. * Then dfs each unvisited node. * Finally, convert the result to an integer array. */ private int[] dfs(int n, List<List<Integer>> adjs) { BitSet hasCycle = new BitSet(1); // Whether there is cycle in graph. Temporary mark. BitSet visited = new BitSet(adjs.size()); // Whether a node is visited. Permanent mark. BitSet onStack = new BitSet(adjs.size()); // Whether the node is on stack already during DFS. // DFS. Deque<Integer> stack = new ArrayDeque<>(); for (int i = adjs.size() - 1; i >= 0; i--) { // Visit each unvisited node. if (!visited.get(i) && !hasOrder(i, adjs, visited, onStack, stack)) { return new int[0]; } } // Convert stack result to int[]. int[] res = new int[adjs.size()]; for (int i = 0; !stack.isEmpty(); i++) { res[i] = stack.pop(); } return res; } /** * DFS. * With node visited states and node on stack states. */ private boolean hasOrder(int from, List<List<Integer>> adjs, BitSet visited, BitSet onStack, Deque<Integer> order) { visited.set(from); // Mark from temporarily. onStack.set(from); for (int to : adjs.get(from)) { if (visited.get(to) == false) { // Adjacent nodes should not be visited. if (hasOrder(to, adjs, visited, onStack, order) == false) { return false; } } else if (onStack.get(to) == true) { // Adjacent nodes should not be on stack. return false; } } onStack.clear(from); order.push(from); // Push to stack finally. return true; } }