• [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;
        }
    }
  • 相关阅读:
    (转载)Linux系统中分离线程的使用
    (转载)Vim的几种模式介绍
    (转载)Linux下检查内存泄漏、系统性能的系列工具
    (转载)Linux 僵尸进程与孤儿进程
    (转载)valgrind,好东西,一般人我不告诉他~~ 选项
    (转载)Linux进程组、作业、会话的理解
    Open a file, and then readin a file tcl tk
    save vars and arrays
    itcl class example
    constructor with args tcl tk
  • 原文地址:https://www.cnblogs.com/airwindow/p/4765364.html
Copyright © 2020-2023  润新知