课程表题:详解

题目:你这个学期必须选修 numCourses 门课程,记为 0numCourses - 1

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai必须 先学习课程 bi

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false

思想:本题的课程可以看成图中的一个个节点,这道题就换成了对的经典的拓扑排序题目,将每一门课看成一个节点,如果学习课程1之前必须学习课程2,则拓扑排序中,2一定出现在1之前,若存在环形就说明不可能存在拓扑结构

  • 使用深度优先搜索,用栈存储搜索完成的节点

  • 对任意一个节点而言,如果它的有向边(比如学习课程1之前必须学习课程2,那么课程2这个节点就必须先入栈,最后判断是否存在这样一个入栈的结果,存在就说明这样的课程有效)的相邻节点(比如课程2)搜索完成,并入栈后,再将该节点(比如课程1)入栈即可

  • 最终从栈顶到栈底的顺序就是一种可行的拓扑排序

  • 对于节点来说有三种状态:

    • 未搜索:还没有搜索到这个节点

    • 搜索中:即还有相邻节点正在搜索

    • 已完成:该节点的相邻节点以及入栈,该节点也已经入栈,保证该节点的顺序永远在相邻节点后入栈(保证要学习课程1之前必须学习课程2

  • 代码中实现:对当前搜索节点u标记为搜索中,遍历相邻节点v

    • 如果v未搜索:开始搜索节点v

    • 如果v搜索中:说明存在一个环,那么就不存在一个拓扑排序(已经对节点u标记为搜索中,搜索的是相邻节点,那么相邻节点就不可能出现搜索中,除非是环,即u指向v,同时v指向了u

    • 如果v已完成:说明v已经入栈,u还没有入栈

    • u的所有相邻节点已经标记为已完成,说明搜索结束,将u也放入栈中,并标记为已完成


总结:这道题的难点在于将课程的学习顺序和拓扑排序联想到一起,因此需要具有发散性思维和扎实的数据结构基础


代码

class Solution {
    //创建一个二维集合,存储每一个节点的后继结点([B,A]:先修课程A,然后才能修课程B,则B是A的后继结点)
    List<List<Integer>> succNode;

    //创建一个标记位数组,用来确定每个节点是否被搜索过:
        //0:未搜索
        //1: 搜索中;此时为环(已经对节点`u`标记为`搜索中`,搜索的是相邻节点,那么相邻节点就不可能出现`搜索中`,除非是环)
        //2: 搜索完成
    int[] flag;

    //创建一个状态变量,表示是否存在符合条件的拓扑排序
    boolean res = true;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //初始化二维集合
        succNode = new ArrayList<>();
        for(int i = 0; i < numCourses; i++){
            succNode.add(new ArrayList<Integer>());
        }
        //将每一个节点的后继节点存入二维集合
        for(int[] info : prerequisites){
        //[A,B]:A是B的后继结点;所以存入集合时,应该是info[1]代表B这个节点的后继结点为info[0]代表A
            succNode.get(info[1]).add(info[0]);
        }

        //标记为一开始都设置为0:表示未搜索
        flag = new int[numCourses];

        //对未搜索的节点进行DFS搜索
        for(int i = 0; i < numCourses; i++){
            if(flag[i] == 0){
                dfs(i);
            }
        }
        return res;
    }

    //对节点 u 进行dfs搜索
    private void dfs(int u){
        //开始搜索时标记位改为1
        flag[u] = 1;

        //搜索该节点的所有未搜索过的后继结点
        //注意:for-each结构具有自动处理 空内容 的情况,不会抛出异常,而是直接跳过代码块的执行
        for(int v : succNode.get(u)){
            if(flag[v] == 0){
                dfs(v);
                //如果某一次搜索后res为false,说明搜索过程中存在环,直接返回false即可
                if(!res){
                    return;
                }
            }//如果flag[v] == 1;说明存在环,直接返回false
            else if(flag[v] == 1){
                res = false;
                return;
            }
        }

        //每一个节点的所有未搜索过的后继结点搜索完成需要标记为2;表示已完成
        flag[u] = 2;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值