[Leetcode] Course Schedule 课程计划

本文探讨了如何通过拓扑排序解决课程先修问题,包括两个实例:课程计划I和课程计划II。课程计划I介绍了如何判断在给定先修关系的情况下是否能够完成所有课程,而课程计划II进一步展示了如何返回正确的课程学习顺序。通过构建有向图并计算每个节点的入度,我们可以找出无环路径,从而确定课程的正确学习顺序。

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

Course Schedule I

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?

For example:

2, [[1,0]] There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.

2, [[1,0],[0,1]] 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.

拓扑排序

复杂度

时间 O(N) 空间 O(N)

思路

先修课问题本质上是一个有向图,如果这个图无环,我们可以根据拓扑排序遍历到所有节点,如果有环则拓扑排序无法完成,遍历到的节点将少于总节点数,因为有的节点是孤岛。这题我们先根据边的关系,建一个图,并计算每个节点的入度,这里用的是数组来建图。然后从入度为0的节点,也就是入口开始广度优先搜索,按照拓扑排序的顺序遍历,最后看遍历过的节点数和总节点数的关系就行了。拓扑排序的使用方法参见外文字典

代码

public class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        ArrayList[] graph = new ArrayList[numCourses];
        int[] indegree = new int[numCourses];
        // 先初始化图,每个赋一个空列表
        for(int i = 0; i < numCourses; i++){
            graph[i] = new ArrayList<Integer>();
        }
        // 根据边建立图,并计算入度
        for(int i = 0; i < prerequisites.length; i++){
            graph[prerequisites[i][1]].add(prerequisites[i][0]);
            indegree[prerequisites[i][0]]++;
        }
        // 找到有向图的入口
        Queue<Integer> queue = new LinkedList<Integer>();
        for(int i = 0; i < indegree.length; i++){
            if(indegree[i] == 0){
                queue.add(i);
            }
        }
        // 按照拓扑排序的顺序,进行广度优先搜索
        int cnt = 0;
        while(!queue.isEmpty()){
            Integer curr = queue.poll();
            cnt++;
            ArrayList<Integer> nexts = graph[curr];
            for(int i = 0; i < nexts.size(); i++){
                int next = nexts.get(i);
                indegree[next]--;
                if(indegree[next] == 0){
                    queue.offer(next); 
                }
            }
        }
        return cnt == numCourses;
    }
}

2018/10

func canFinish(numCourses int, prerequisites [][]int) bool {
    graph := make(map[int][]int)
    indegree := make([]int, numCourses)
    // build the graph, count the indegree for each course
    for _, prerequisite := range prerequisites {
        course1 := prerequisite[0]
        course2 := prerequisite[1]
        if graph[course2] != nil {
            graph[course2] = append(graph[course2], course1)
        } else {
            graph[course2] = []int{course1}
        }
        indegree[course1]++
    }
    // find out those with 0 indegree as roots of the graph
    var queue []int
    for i, degree := range indegree {
        if degree == 0 {
            queue = append(queue, i)
        }
    }
    // traverse the graph from each root, decrement the indegree for each leaf
    count := 0
    for len(queue) > 0 {
        curr := queue[0]
        queue = queue[1:]
        count++
        for _, course := range graph[curr] {
            indegree[course]--
            // once a node's indegree is 0, it becomes a root too
            if indegree[course] == 0 {
                queue = append(queue, course)
            }
        }
    }
    // if all nodes have been the root before, we traversed the entire graph
    return count == numCourses
}

Course Schedule II

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, return the ordering of courses you should take to finish all courses.

There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.

For example:

2, [[1,0]] There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1]

4, [[1,0],[2,0],[3,1],[3,2]] There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3].

拓扑排序

复杂度

时间 O(N) 空间 O(N)

思路

和I不同的是,这题要返回路径。其实也没有什么不同的,不过是多加一个数组,记录广度优先搜索的遍历顺序就行了。

注意

当没有先修课的时候,随便返回一个顺序就行了,所以初始化的时候就初始化为升序序列,方便后面直接返回。

代码

public class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] res = new int[numCourses];
        ArrayList<Integer>[] graph = new ArrayList[numCourses];
        int[] indegree = new int[numCourses];
        for(int i = 0; i < prerequisites.length; i++){
            if(graph[prerequisites[i][1]] == null){
                graph[prerequisites[i][1]] = new ArrayList<Integer>();
            }
            graph[prerequisites[i][1]].add(prerequisites[i][0]);
            indegree[prerequisites[i][0]]++;
        }
        Queue<Integer> queue = new LinkedList<Integer>();
        for(int i = 0; i < indegree.length; i++){
            if(indegree[i] == 0){
                queue.add(i);
            }
        }
        // 用idx记录输出数组的下标
        int idx = 0;
        while(!queue.isEmpty()){
            Integer curr = queue.poll();
            res[idx++] = curr;
            if(graph[curr] == null) continue;
            for(Integer next : graph[curr]){
                if(--indegree[next] == 0){
                    queue.offer(next); 
                }
            }
        }
        // 如果有环则返回空数组
        return idx != numCourses ? new int[0] : res;
    }
}
### LeetCode 拓扑排序题目解析 #### 课程表 (Course Schedule) 在LeetCode上的经典问题之一是《课程表》[^4]。该问题描述如下: 给定 `n`门课程以及一些先修课的要求,判断是否可以完成所有课程的学习。 ##### 思路分析 这个问题可以通过检测是否存在有向无环图(DAG)来解决。具体来说,如果能够找到一种拓扑排序方式,则意味着这些课程是可以按照一定顺序全部学完的;反之如果有环存在,则无法完成所有的课程学习。 为了实现这一点,通常会采用邻接矩阵或者邻接表的形式构建图结构,并利用深度优先搜索(DFS)[^2] 或广度优先搜索(BFS)[^1] 来尝试获得一个有效的拓扑序列。 对于BFS方法,在初始化阶段需要找出入度为零的节点加入队列中作为起点,之后不断移除当前处理过的节点并更新其他相连节点的入度直到遍历结束或发现矛盾为止[^5]。 ```java public boolean canFinish(int numCourses, int[][] prerequisites) { List<Integer>[] adj = new ArrayList[numCourses]; int[] indegrees = new int[numCourses]; // 构建邻接表和计算各结点入度数 for (int[] edge : prerequisites) { if (adj[edge[1]] == null) adj[edge[1]] = new ArrayList<>(); adj[edge[1]].add(edge[0]); ++indegrees[edge[0]]; } Queue<Integer> queue = new LinkedList<>(); // 将所有入度为0的顶点入队 for (int i = 0; i < numCourses; ++i){ if(indegrees[i]==0)queue.offer(i); } while(!queue.isEmpty()){ Integer cur=queue.poll(); --numCourses; if(adj[cur]!=null){ for(Integer next:adj[cur]){ if(--indegrees[next]==0){ queue.offer(next); } } } } return numCourses==0; } ``` 这段代码实现了基于Kahn算法(即宽度优先搜索)来进行拓扑排序的过程。它首先统计每个节点的入度数目,并把那些没有任何前驱依赖关系(也就是入度等于0)的节点放进初始队列里。接着逐个取出队首元素进行访问操作——减少其指向的目标节点们的剩余未满足条件数量(即减去它们各自的入度值)。一旦某个目标节点变得不再受约束(它的新入度变为0),就立即将这个节点追加到待处理集合当中继续考察下去。最终当整个流程结束后,只要还剩下没被触及过的东西就意味着原图中含有至少一个闭环路径,从而导致任务失败;否则便成功找到了一组可行方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值