[LeetCode#207]Course Schedule

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;
    }
}

 

posted @ 2015-08-28 04:04  airforce  阅读(215)  评论(0编辑  收藏  举报