标签:
Problem:
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?
For example:
2, [[1,0]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.
2, [[1,0],[0,1]]
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.
Note:
The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
Analysis:
reference: http://www.programcreek.com/2014/05/leetcode-course-schedule-java/ The idea behind this problem is not hard, we can treat it as a graph. The problem is to detect if there is a circle exist. Since the degrees of a node‘s out/in edges could be greatly different, We should consider clear all case. Initial Wrong Idea: Take advantage of Queue, we use BFS method to solve this problem through following pattern: step 1: Scan the prerequisites matrix, to identify out the courses that does not have prerequisites. And add them into queue. step 2: Pop a course out, and unlock all courses based on it as prerequisite, and add all of them into queue. step 3: When the queue is empty, check the count of poped courses. to decide if all courses could be studied through certain order. Solution: public boolean canFinish(int numCourses, int[][] prerequisites) { if (prerequisites == null) throw new IllegalArgumentException("Invalid prerequisites matrix"); int m = prerequisites.length; boolean[] used = new boolean[m]; int count = 0; if (numCourses == 0 || m == 0) return true; Queue<Integer> queue = new LinkedList<Integer> (); for (int i = 0; i < m; i++) { boolean no_pre = true; for (int j = 0; j < m; j++) { no_pre = no_pre && (prerequisites[i][j] == 0); } if (no_pre) { queue.offer(i); } } if (queue.size() == 0) return false; while (!queue.isEmpty()) { int cur = queue.poll(); used[cur] = true; count++; unlockCourse(prerequisites, queue, cur, used); } return count == numCourses; } public void unlockCourse(int[][] prerequisites, Queue<Integer> queue, int cur, boolean[] used) { int m = prerequisites.length; for (int i = 0; i < m; i++) { //search through BFS must tag the visited element if (prerequisites[i][cur] == 1 && used[i] == false) queue.offer(i); } } The above solution is wrong for following reasons: 1. the graph is represented in edges rather than adjacency matrix. 2. a course may have more than one prerequisites!!! You can‘t unlock all courses for a unlocked course. while (!queue.isEmpty()) { int cur = queue.poll(); used[cur] = true; count++; unlockCourse(prerequisites, queue, cur, used); } A fix: The above actually has laid a very good foundation for us to fix. The same idea: Use a queue for recording unlocked courses, when a course poped out from the queue, we increase the count of unlocked course. Besides the queue, we also take advantage of a counter array, which records the left prerequisites for a course. Only when the prerequisites count of a course equal to 0, we treat it as an unlocked course and add it into queue. Step 1: count the prerequisites for each course. int[] pre_counter = new int[numCourses]; int len = prerequisites.length; for (int i = 0; i < len; i++) { pre_counter[prerequisites[i][0]]++; } Step 2: put courses that have no prerequisites into the queue. for (int i = 0; i < numCourses; i++) { if (pre_counter[i] == 0) queue.offer(i); } Step 3: unlock courses through unlocked courses. while (!queue.isEmpty()) { int cur = queue.poll(); count++; for (int i = 0; i < len; i++) { //note the logic here, must [i][1] == cur, guarantee repeately add in to queue if (prerequisites[i][1] == cur) { pre_counter[prerequisites[i][0]]--; if (pre_counter[prerequisites[i][0]] == 0) queue.offer(prerequisites[i][0]); } } } } Logic pitfall: I have made following logic errors, which result in infinite loop. if (prerequisites[i][1] == cur) { pre_counter[prerequisites[i][0]]--; } if (pre_counter[prerequisites[i][0]] == 0) { queue.offer(prerequisites[i][0]); } The most common mistakes in using BFS is to revisit node and add it into queue again. The mistake I have made at here is a good example. for (int i = 0; i < len; i++) { ... } This would cause us to revisit pre_counter[prerequisites[i][0]] time and time, and keep on add prerequisites[i][0] into our queue. Thus we usually use a visited array to indicate that a course has alredy been unlocked and visisted. But for this problem, we can do it in a more simple way. Fix method: for (int i = 0; i < len; i++) { if (prerequisites[i][1] == cur) { pre_counter[prerequisites[i][0]]--; if (pre_counter[prerequisites[i][0]] == 0) queue.offer(prerequisites[i][0]); } } Why it works? First, let us assume the same cur would only be poped once before enter the loop. for (int i = 0; i < numCourses; i++) { if (pre_counter[i] == 0) queue.offer(i); } Only for "prerequisites[i][1] == cur" and it just solved a unlock courses(we can say its the last prerequisites course). we add the course into queue. if (pre_counter[prerequisites[i][0]] == 0) queue.offer(prerequisites[i][0]); Which means, only when a course was just unlocked, we add it into queue. No other times of adding an element into queue. Note: the cur would only be poped out, since we add it only once. Another fix method. use a visited array. boolean[] visited = new boolean[numCourses]; while (!queue.isEmpty()) { int cur = queue.poll(); visited[cur] = true; count++; for (int i = 0; i < len; i++) { //note the logic here, must [i][1] == cur, guarantee repeately add in to queue if (prerequisites[i][1] == cur) pre_counter[prerequisites[i][0]]--; if (pre_counter[prerequisites[i][0]] == 0 && visited[prerequisites[i][0]] == false) queue.offer(prerequisites[i][0]); } } Even though this this would solve infinte loop problem, but it still could exceed time when the size of courses is large. Then reason is that: we keep on visit on all "pre_counter[prerequisites[i][0]]" no matter if (prerequisites[i][1] == cur) This method is rude and wrong, we should try to avoid uncessary check. if (prerequisites[i][1] == cur) { pre_counter[prerequisites[i][0]]--; if (pre_counter[prerequisites[i][0]] == 0 && visited[prerequisites[i][0]] == false) queue.offer(prerequisites[i][0]); }
Solution:
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { if (prerequisites == null) throw new IllegalArgumentException("the prerequisites matrix is not valid"); int len = prerequisites.length; boolean[] visited = new boolean[numCourses]; if (numCourses == 0 || len == 0) return true; int[] pre_counter = new int[numCourses]; int count = 0; Queue<Integer> queue = new LinkedList<Integer> (); for (int i = 0; i < len; i++) { pre_counter[prerequisites[i][0]]++; } for (int i = 0; i < numCourses; i++) { if (pre_counter[i] == 0) { queue.offer(i); } } while (!queue.isEmpty()) { int cur = queue.poll(); visited[cur] = true; count++; for (int i = 0; i < len; i++) { //note the logic here, must [i][1] == cur, guarantee repeately add in to queue if (prerequisites[i][1] == cur) { pre_counter[prerequisites[i][0]]--; if (pre_counter[prerequisites[i][0]] == 0 && visited[prerequisites[i][0]] == false) queue.offer(prerequisites[i][0]); } } } return count == numCourses; } }
标签:
原文地址:http://www.cnblogs.com/airwindow/p/4765364.html