Baozi Training Leetcode solution 207. Course Schedule

Problem Statement 

There are a total of numCourses courses you have to take, labeled from 0 to numCourses-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 1:

Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0, and to take course 0 you should
             also have finished course 1. So it is impossible.

 

Constraints:

  • 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.
  • 1 <= numCourses <= 10^5

 Problem link

Video Tutorial

You can find the detailed video tutorial here

Thought Process

It is a classic dependency graph problem. We can translate this problem to direct if there is a cycle in a directed graph or not. A text book solution is Kahn's algorithm for topological sorting. We can have a simple way to represent the graph or use a more proper adjacency lists (a little bit overkill for this problem though)

Solutions 

Use adjacency lists BFS

 1 private class Node {
 2     public int val;
 3     public List<Node> neighbors;
 4 
 5     public Node(int val) {
 6         this.val = val;
 7         this.neighbors = new ArrayList<>();
 8     }
 9 }
10 
11 private List<Node> buildGraph(int num, int[][] prerequisites) {
12     List<Node> graph = new ArrayList<>();
13 
14     for (int i = 0; i < num; i++) {
15         graph.add(new Node(i));
16     }
17 
18     for (int i = 0; i < prerequisites.length; i++) {
19         graph.get(prerequisites[i][0]).neighbors.add(graph.get(prerequisites[i][1]));
20     }
21 
22     return graph;
23 }
24 
25 // model to a Nodes, still BFS with topological order to detect if a graph is a DAG
26 // https://www.geeksforgeeks.org/detect-cycle-in-a-directed-graph-using-bfs/
27 public boolean canFinishGraph(int numCourses, int[][] prerequisites) {
28     if (numCourses <= 1 || prerequisites.length == 0 || prerequisites[0].length == 0) {
29         return true;
30     }
31     List<Node> graph = this.buildGraph(numCourses, prerequisites);
32 
33     Queue<Node> q = new LinkedList<>();
34 
35     for (Node n : graph) {
36         if (n.neighbors.size() == 0) {
37             q.add(n);
38         }
39     }
40 
41     Set<Node> visited = new HashSet<>();
42     while (!q.isEmpty()) {
43         Node cur = q.poll();
44 
45         visited.add(cur);
46 
47         for (Node n : graph) {
48             if (n.neighbors.contains(cur)) {
49                 n.neighbors.remove(cur);
50                 // only enqueue the nodes while there is no more neighbors
51                 if (n.neighbors.size() == 0) {
52                     q.add(n);
53                 }
54             }
55         }
56 
57     }
58 
59     return visited.size() == numCourses;
60 }
61 @shixiaoyu
62  

 

Time Complexity: O(V), since each vertex is visited only once during BFS
Space Complexity: O(V+E) since we use adjacency lists to represent a directed graph

Use simple hashmap BFS

 1 public boolean canFinishBfsTopoSort(int numCourses, int[][] prerequisites) {
 2     if (numCourses <= 1 || prerequisites.length == 0 || prerequisites[0].length == 0) {
 3         return true;
 4     }
 5 
 6     Map<Integer, Set<Integer>> graph = new HashMap<>();
 7 
 8     // could be extracted into a build graph function
 9     for (int i = 0; i < numCourses; i++) {
10         graph.put(i, new HashSet<>());
11     }
12 
13     for (int i = 0; i < prerequisites.length; i++) {
14         graph.get(prerequisites[i][0]).add(prerequisites[i][1]);
15     }
16 
17     int coursesRemaining = numCourses;
18     Queue<Integer> queue = new LinkedList<>();
19 
20     // initialize
21     for (Map.Entry<Integer, Set<Integer>> entry : graph.entrySet()) {
22         // this is the reverse as the graph topological order, but it is the actual problem solving order
23         // e.g., a->b, -> reads as depends on, meaning you have to finish b to do a, so it will print out b, a
24         if (entry.getValue().size() == 0) { 
25             queue.offer(entry.getKey());
26             coursesRemaining--;
27         }
28     }
29 
30     // BFS
31     while (!queue.isEmpty()) {
32         int key = queue.poll();
33         System.out.println("** key: " + key);
34         for (Map.Entry<Integer, Set<Integer>> entry : graph.entrySet()) {
35             Set<Integer> curDependencies = entry.getValue();
36 
37             if (curDependencies.contains(key)) {
38                 curDependencies.remove((Integer)key); // need to cast or else it will be remove(int index)
39                 if (curDependencies.size() == 0) { // immediately check to avoid another loop
40                     queue.offer(entry.getKey());
41                     coursesRemaining--;
42                 }
43             }
44         }
45     }
46 
47     return coursesRemaining == 0;
48 }

 

Time Complexity: O(V), since each vertex is visited only once during BFS

Space Complexity: O(V) since we are using a hashmap
 

Use recursion DFS

 1 // This is a DFS solution, basically just like detecting if a graph has loops.
 2 // Used a lookup table prune, with lookup, this is O(V + E), same as BFS since each node is visited once and only once
 3 public boolean canFinish(int numCourses, int[][] prerequisites) {
 4     if (numCourses <= 1 || prerequisites.length == 0 || prerequisites[0].length == 0) {
 5         return true;
 6     }
 7 
 8     // this is adjacency list, used a set to dedup
 9     Map<Integer, Set<Integer>> graph = new HashMap<>();
10 
11     for (int i = 0; i < numCourses; i++) {
12         graph.put(i, new HashSet<>());
13     }
14 
15     for (int i = 0; i < prerequisites.length; i++) {
16         graph.get(prerequisites[i][0]).add(prerequisites[i][1]);
17     }
18 
19     Map<Integer, Boolean> lookup = new HashMap<>();
20 
21     boolean[] visited = new boolean[numCourses];
22     for (int i = 0; i < numCourses; i++) {
23         if (!this.explore(i, graph, visited, lookup)) {
24             return false;
25         }
26     }
27     return true;
28 }
29 
30 // This is the DFS way to solve it, This could also generate a topological order, think about post order way
31 // return false if there is a loop, true if no loop
32 private boolean explore(int vertex, Map<Integer, Set<Integer>> graph, boolean[] visited, Map<Integer, Boolean> lookup) {
33     // keeps track of if the node has been visited or not, 
34     // with this lookup, it's pruning (memorization so that we don't need to re-calculate previously visited node)
35     /*
36            1
37          /    \
38        0       3  - 4
39          \ 2  /
40       e.g., when recursion 0 -> 1 -> 3 -> 4
41                  in 2nd round, 0 -> 2 -> 3(could directly return) 
42      */
43     if (lookup.containsKey(vertex)) {  
44         return lookup.get(vertex);
45     }
46 
47     visited[vertex] = true; // keeps track of visited or not during recursion
48 
49     Set<Integer> dependencies = graph.get(vertex);
50     for (Integer i : dependencies) {
51         if (visited[i] || !this.explore(i, graph, visited, lookup)) {
52             return false;
53         }
54     }
55     visited[vertex] = false;
56     lookup.put(vertex, true);
57     return true;
58 }

 

Time Complexity: O(V), since each vertex is visited only once during BFS
Space Complexity: O(V) since we used a lookup hashmap for memorization purpose (not considering function stack space)
 

References


posted @ 2020-05-18 08:03  包子模拟面试  阅读(260)  评论(0编辑  收藏  举报