题目描述

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

提示:

输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
1 <= numCourses <= 10^5


思路

  • 可以理解为,是否是有向无环图(DAG)
  • 通过拓扑排序判断
  • 可以用邻接表(adjacency)降低算法复杂度

方法一:入度表(广度优先算法)

  1. 根据每个节点设计入度表indegrees
  2. 借助队列,将所有入度为0节点入队
  3. queue不为空,出队,删除此节点课程(已完成该节点拓扑)
  1. 实际上只是将入度-1。
  2. 入度-1后邻接节点入度为0,说明前驱已全部删除,将cur入队
  1. pre出队时,执行numCourses–
  1. 若为有向无环图,则所有节点一定都入队并出队,完成拓扑排序。
  2. 当numCourses==0.说明课程可以成功安排。

java 课程表控件 java实现课程表_leetcode

class Solution {
    //有向无环图结构
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //计算入度
        int[] indegrees = new int[numCourses];
        //设计邻接表
        List<List<Integer>> adjacency = new ArrayList();
        //利于队列结构进行拓扑排序
        Queue<Integer> queue = new LinkedList();
        //先在邻接表设计各个课程的节点
        for(int i=0;i<numCourses;i++){
            adjacency.add(new ArrayList());
        }
        //计算入度及邻接情况
        for(int[] cp:prerequisites){
            indegrees[cp[0]]++;
            adjacency.get(cp[1]).add(cp[0]);
        }
        //逐个课程找出入度为0作为拓扑起点
        for(int i=0; i<numCourses; i++){
            if(indegrees[i]==0){
                queue.add(i);
            }
        }
        //取出拓扑起点,统计课程数-1,将邻接节点的入度-1,判断该节点是否满足拓扑排序,满足则加入队列
        while(!queue.isEmpty()){
            int cur = queue.poll();
            numCourses--;
            for(int tmp:adjacency.get(cur)){
                if(--indegrees[tmp]==0) queue.add(tmp);
            }
        }
        //若满足拓扑排序,则所有节点都经过入队出队。
        return numCourses==0;
    }
}

java 课程表控件 java实现课程表_dfs_02

方法二:深度优先算法

判断图中是否有环

  1. 借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:
    未被 DFS 访问:i == 0;
    已被其他节点启动的 DFS 访问:i == -1;
    已被当前节点启动的 DFS 访问:i == 1。
  2. 对 numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回 False。DFS 流程;
  1. 终止条件:
    当 flag[i] == -1,说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 True。
    当 flag[i] == 1,说明在本轮 DFS 搜索中节点 i 被第 2次访问,即 课程安排图有环 ,直接返回 False。
  2. 将当前访问节点 i 对应 flag[i] 置 1,即标记其被本轮 DFS 访问过;
  3. 递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 False;
  4. 当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 -1并返回 True。
  1. 若整个图 DFS 结束并未发现环,返回 True。

java 课程表控件 java实现课程表_dfs_03

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adjacency = new ArrayList();
        for(int i=0;i<numCourses;i++){
            adjacency.add(new ArrayList());
        }
        //利用标记找环
        int[] flags = new int[numCourses];
        for(int[] cp:prerequisites){
            adjacency.get(cp[1]).add(cp[0]);
        }
        //深度搜索,若出现false,则整体false
        for(int i=0; i<numCourses; i++){
            if(!dfs(flags,i,adjacency)) return false;
        }
        return true;
    }
    public boolean dfs(int[] flags,int i, List<List<Integer>> adjacency){
        //终止条件,若访问的为1,则说明出现环,返回false,为-1则停止当前递归。(剪枝)
        if(flags[i]==1) return false;
        if(flags[i]==-1) return true;
        //将当前节点置为1
        flags[i]=1;
        //逐个访问邻接节点,进行深搜
        for(Integer cur:adjacency.get(i)){
             if(!dfs(flags,cur,adjacency)) return false;
        } 
        //回溯,用-1表示当前及后面节点已经访问过。
        flags[i]=-1;
        return true;
    }
}

java 课程表控件 java实现课程表_leetcode_04