天天看点

Leetcode207. Course Schedule

Leetcode207. Course Schedule

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?

Example 1:

Input: 2, [[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: 2, [[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.
           

Note:

  • 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,3],[1,4],[2,4],[3,5],[3,6],[4,6]]

,画出有向图如下,箭头指向的是需要先上的课。

Leetcode207. Course Schedule

依次删除那些没有预修课的课,比如删去5,6。

Leetcode207. Course Schedule

然后 3 和 4 就可以上了,同样的道理再把 3 和 4 删去。接下来就可以去学 1 和 2 了。因此可以完成所有的课。

用邻接表表示图。此外,我们不需要真的去删除节点,我们可以用

outNum

变量记录所有节点的先修课门数。当删除一个节点的时候,就将相应节点的先修课个数减一即可。最后只需要判断所有的节点的先修课门数是否全部是 0 即可。

public boolean canFinish(int numCourses, int[][] prerequisites) {
    //保存每个节点的先修课个数,也就是出度
    HashMap<Integer, Integer> outNum = new HashMap<>();
    //保存以 key 为先修课的列表,也就是入度的节点
    HashMap<Integer, ArrayList<Integer>> inNodes = new HashMap<>();
    //保存所有节点
    HashSet<Integer> set = new HashSet<>();
    int rows = prerequisites.length;
    for (int i = 0; i < rows; i++) {
        int key = prerequisites[i][0];
        int value = prerequisites[i][1];
        set.add(key);
        set.add(value);
        if (!outNum.containsKey(key)) {
            outNum.put(key, 0);
        }
        if (!outNum.containsKey(value)) {
            outNum.put(value, 0);
        }
        //当前节点先修课个数加一
        int num = outNum.get(key);
        outNum.put(key, num + 1);

        if (!inNodes.containsKey(value)) {
            inNodes.put(value, new ArrayList<>());
        }
        //更新以 value 为先修课的列表
        ArrayList<Integer> list = inNodes.get(value);
        list.add(key);
    }

    //将当前先修课个数为 0 的课加入到队列中
    Queue<Integer> queue = new LinkedList<>();
    for (int k : set) {
        if (outNum.get(k) == 0) {
            queue.offer(k);
        }
    }
    while (!queue.isEmpty()) {
        //队列拿出来的课代表要删除的节点
        //要删除的节点的 list 中所有课的先修课个数减一
        int v = queue.poll();
        ArrayList<Integer> list = inNodes.getOrDefault(v, new ArrayList<>());

        for (int k : list) {
            int num = outNum.get(k);
            //当前课的先修课要变成 0, 加入队列
            if (num == 1) {
                queue.offer(k);
            }
            //当前课的先修课个数减一
            outNum.put(k, num - 1);
        }
    }

    //判断所有课的先修课的个数是否为 0 
    for (int k : set) {
        if (outNum.get(k) != 0) {
            return false;
        }
    }
    return true;
}
           

解法二 DFS

从某门课开始遍历,我们通过 DFS 一条路径一条路径的判断,保证过程中没有遇到环。
Leetcode207. Course Schedule
深度优先遍历 1,相当于 3 条路径

1 -> 3 -> 5,1 -> 3 -> 6,1 -> 4 -> 6。

深度优先遍历 2,相当于 1 条路径

2 -> 4 -> 6。

深度优先遍历 3,相当于 2 条路径

3 -> 5,3 -> 6。

深度优先遍历 4,相当于 1 条路径

4 -> 6。

深度优先遍历 5,相当于 1 条路径

5。

深度优先遍历 6,相当于 1 条路径

6。
           

什么情况下不能完成所有课程呢?某条路径出现了环,如下图。

Leetcode207. Course Schedule

出现了 1 -> 3 -> 6 -> 3。所以不能学完所有课程。

代码的话,用邻接表表示图。通过递归实现

DFS

,用

visited

存储当前路径上的节点。

同时用

visitedFinish

表示可以学完的课程,起到优化算法的作用。

public boolean canFinish(int numCourses, int[][] prerequisites) {
    HashMap<Integer, ArrayList<Integer>> outNodes = new HashMap<>();
    HashSet<Integer> set = new HashSet<>();
    int rows = prerequisites.length;
    for (int i = 0; i < rows; i++) {
        int key = prerequisites[i][0];
        int value = prerequisites[i][1];
        set.add(key); 
        if (!outNodes.containsKey(key)) {
            outNodes.put(key, new ArrayList<>());
        } 
        //存储当前节点的所有先修课程
        ArrayList<Integer> list = outNodes.get(key);
        list.add(value);
    }

    HashSet<Integer> visitedFinish = new HashSet<>();
    //判断每一门课
    for (int k : set) {
        if (!dfs(k, outNodes, new HashSet<>(), visitedFinish)) {
            return false;
        }
        visitedFinish.add(k);
    }
    return true;
}

private boolean dfs(int start, HashMap<Integer, ArrayList<Integer>> outNodes, HashSet<Integer> visited,
                    HashSet<Integer> visitedFinish) {
    //已经处理过或者到了叶子节点
    if (visitedFinish.contains(start) || !outNodes.containsKey(start)) {
        return true;
    }
    //出现了环
    if (visited.contains(start)) {
        return false;
    }
    //将当前节点加入路径
    visited.add(start);
    ArrayList<Integer> list = outNodes.get(start);
    for (int k : list) {
        if(!dfs(k, outNodes, visited, visitedFinish)){
            return false;
        }
    }
    visited.remove(start);
    return true;
}