【力扣】207.课程表

这道题目是关于在有向图中判断是否存在环,从而解决课程选修的依赖问题。通过解析先修课程的条件,将问题转化为寻找图的环。示例展示了当存在环时(如课程0依赖课程1,课程1依赖课程0)无法完成所有课程。解题策略包括构建邻接表和查找环,其中查找环使用了状态数组记录节点的遍历状态,若发现状态为1的节点形成环,则表示存在循环依赖,无法完成课程学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

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

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

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

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

示例 2:

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

分析

关键的信息在“要学习课程a_i 则 必须 先学习课程b_i”,这就意味着课程之间存在一些依赖关系,在示例2中,课程0依赖课程1,课程1依赖课程0,出现了循环依赖的情况,因此不能完成学习。看到循环依赖我们很容易想到环,进而想到在有向图中会出现环。于是可以将问题建模为在有向图中找环。这就有两种方法,第一种是拓扑排序,存在拓扑排序的图中一定没有环。第二种是直接找环,这里我们选用第二种方法,直接找环。

解题

主要分为2个步骤1.根据 prerequisites构建邻接表 2.查找环

构建邻接表时,要注意

图中的节点个数为numCourses,即课程数。

prerequisites给的是边信息,prerequisites[i] = [a_i, b_i]表示存在一条从a_i指向b_i的边,因此b_ia_i的一个邻接节点,其余以此类推。

查找环:在对图的遍历过程种,对于一个节点,其有三种状态,0表示还未遍历,1表示已经遍历结束(所有邻接节点都已遍历),2表示正在遍历(还有邻接节点未遍历),那么如果在遍历状态为1的A节点时,再次遍历到另一个状态为1的B节点,就表示两个节点之间存在环,这并不难理解。我们可以用一个长度为numCourses的int数组来记录每个节点的状态。

具体代码如下:

public boolean canFinish(int numCourses, int[][] prerequisites) {
    if (prerequisites.length == 0 || prerequisites.length == 1) return true;

    List<List<Integer>> adj = new ArrayList<>();
    
    int[] visited = new int[numCourses];

    // 添加节点,节点个数为numCourses
    for (int i = 0; i < numCourses; i++) {
        adj.add(new ArrayList<>());
    }
    
    // 添加邻接节点
    for (int i = 0; i < prerequisites.length; i++) {
        adj.get(prerequisites[i][0]).add(prerequisites[i][1]);
    }

    // 遍历图中节点
    for (int i = 0; i < numCourses; i++) {
        if(findCycle(i, visited, adj)) return false;
    }
    return true;
}

public boolean findCycle(int node, int[] visited, List<List<Integer>> adj) {
    // 0表示还未访问
    // 1表示已访问过
    // 2表示正在访问
    if (visited[node] == 2) return true; // 遍历到某正在访问的节点,找到环,返回true
    if (visited[node] == 1) return false; // 遍历到访问结束的节点,未找到环,返回false
    
    // 标记正在遍历的节点
    visited[node] = 2;

    // 遍历邻接节点
    for (Integer next : adj.get(node)) {
        if(findCycle(next, visited, adj)) return true; //如果找到环,返回true
    }

    visited[node] = 1; // 标记访问结束的节点
    return false; // 未找到环,返回false
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值