LeetCode210:课程表(Java)

如题image-20220405215303452

  • 解读题意:题目中的numCourses表示需要修读的课程总数,且课程编号是从0-numCourses-1的,prerequisites表示每两门课之间的依赖关系。我们需要给出各门课程学习的先后顺序,保证合理完成全部课程。如果无法完成所有课程,就返回空数组。

  • 什么时候无法完成

    • 当课程之间存在循环依赖的时候就无法完成,例如完成[1,2],[2,1]这样,因此我们可以将这些依赖关系抽象成一个有向图,需要做的就是检测图中有没有环
  • 如果可以完成,如何记录下来先后顺序

    • 将所有课程依赖关系看成图,那么完成课程的顺序就是这个有向图的拓扑排序顺序,而拓扑排序就是一次遍历每个入度为0的结点,入度为0即不被任何结点指向。

    • 有向图的拓扑排序可以利用DFS深度优先搜索或者BFS广度优先搜索进行,这里我们是用DFS

      • 对于DFS来说,图的拓扑排序可以理解为后序遍历的结果然后反转。因为后序遍历先遍历叶子节点,叶子节点其实就是出度为0的结点,否则就不是叶子,也就是说一次遍历出度为翻0的所有借点,反转后就是根据入度为0的顺序遍历了。如下:

        public void traverse(List<Integer>[] graph,int s){
            for (Integer child : graph[s]) {
                traverse(graph,child);
            }
            arrayList.add(s);//在遍历代码后的位置将当前结点加入集合中,这里就是后序遍历
        }
        
      • 注意:不能是先序遍历,因为有可能一个父母指向同一个孩子,此时先序遍历会直接遍历一个父母后就遍历孩子,此时该孩子还被另一个父结点指向,入度为1,遍历错误

  • 代码如下

    class Solution {
        boolean[] visited;//用来记录遍历过的结点
        boolean Circle = false;//如果有环就将它置为true
        boolean[] onPath;//用来判断是否存在环
        ArrayList<Integer> arrayList;//用来记录后序遍历的每个结点
        public int[] findOrder(int numCourses, int[][] prerequisites) {
            int[] res = new int[numCourses];//用来返回最后的结果
            arrayList = new ArrayList<>(numCourses);
            visited = new boolean[numCourses];
            //visited数组此处不用来判断是否存在环,而是因为我们无法保证图联通,需要将所有节点依次作为
            // 开始结点遍历图,此时如果依次遍历abc,下一次以b开始又会遍历bc,这就出现了重复遍历
            //visited数组可以避免重复遍历的问题。
            onPath = new boolean[numCourses];
            //因此使用onPath记录 当前 走过的路径,每次遍历到新结点时判断onPath是否为true,如果是
            //就代表之前遍历过这个结点,因此存在环,然后将Circle置为true。
            List<Integer>[] graph = buildGraph(numCourses, prerequisites);
            for (int i = 0; i < numCourses; i++) {
                traverse(graph,i);
            }
            if (Circle)return new int[]{};
            Collections.reverse(arrayList);
            for (int i = 0; i < arrayList.size(); i++) {
                res[i] = arrayList.get(i);
            }
            return res;
        }
        //建图,使用链表法,每个结点作为开始结点存在在List数组中,后面依次连接着它指向的结点
        public List<Integer>[] buildGraph(int numCourses,int[][] prerequisites){
            ArrayList<Integer>[] res = new ArrayList[numCourses];
            for (int i = 0; i < res.length; i++) {
                res[i] = new ArrayList<>();
            }
            for (int[] p : prerequisites){
                res[p[1]].add(p[0]);
                //由于学习p[0]之前需要学习p[1],因此由p[1]指向p[0]
            }
            return res;
        }
        //遍历图判断是否有环,并且记录后序遍历的结点
        public void traverse(List<Integer>[] graph,int s){
            if (onPath[s]){
                Circle = true;
                //不是return,而是用全局变量记录,因为可能只是一个分支有环,return只是让当前分支不再继续,
                //此时我们应该让其它分支也不用再继续遍历。
    
                //要注意,我们的onPath数组需要在退出的时候清除标记,即只记录当前路径,因为如果和visited
                // 一样的话,就无法判断是有环而重复还是从子节点开始遍历而重复了,因为我们无法判断图是否连通,
                // 需要将所有结点遍历,如果遍历到了子节点那么也是ture,因此onPath只记录当前遍历的路径。
    
                //注意:这里必须将判断环写在头而不是下面的visited写在头,因为我们只是让visited起到避免
                //重复遍历子节点的问题,但他仍然是可以在一次遍历中检测环的,这是它的本职工作,我们只是不用罢了。
                //如果将visited放在头部,那么碰到环还是可以被他检测出来,那么它无法分辨到底是重复遍历子节点导致
                //条件成立还是因为环导致条件成立。
            }
            if(visited[s] || Circle){
                return;
            }
            visited[s] = true;
            onPath[s] = true;
            for (Integer child : graph[s]) {
                traverse(graph,child);
            }
            arrayList.add(s);//将当前结点加入集合中
            onPath[s] = false;
        }
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一酒。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值