您的位置:首页 > 其它

[LeetCode#207]Course Schedule

2015-08-28 04:04 302 查看
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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: