【LeetCode】课程表 II [M](拓扑排序)

166 篇文章 0 订阅
78 篇文章 37 订阅

210. 课程表 II - 力扣(LeetCode)

一、题目

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。

例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。

示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

示例 3:
输入:numCourses = 1, prerequisites = []
输出:[0]

提示:

二、代码

class Solution {
    public class Course {
        // 该课程在拓扑序列中的入度
        public int in;
        // 该课程编号
        public int courseId;
        // 该课程在拓扑序列中的下一个课程集合
        public List<Course> nexts;

        public Course(int courseId) {
            this.in = 0;
            this.courseId = courseId;
            nexts = new ArrayList<Course>();
        }
    }
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // 要返回的拓扑排序的答案
        int[] ans = new int[numCourses];
        // 此时的课程有0~numCourses - 1这几个编号
        // 先将这几个编号顺序加入ans
        for (int i = 0; i < numCourses; i++) {
			ans[i] = i;
		}
        // 如果所有的课程之间没有学习的先后依赖关系,就直接返回ans
        if (prerequisites == null || prerequisites.length == 0) {
            return ans;
        }

        // 构建课程编号和其对应的Course对象的关联表
        HashMap<Integer, Course> map = new HashMap<>();
        // 遍历prerequisites数组,构造拓扑序列的图
        for (int[] a : prerequisites) {
            // 要想学课程a[0],就要先学课程a[1],所以在拓扑序列中a[1]在a[0]前面
            // 源节点
            int from = a[1];
            // 目的节点
            int to = a[0];

            // 如果from课程还没有为其创建Course对象,就创建并加入到map中
            if (!map.containsKey(from)) {
                map.put(from, new Course(from));
            }
            // 如果to课程还没有为其创建Course对象,就创建并加入到map中
            if (!map.containsKey(to)) {
                map.put(to, new Course(to));
            }

            // 获取from和to课程对象
            Course f = map.get(from);
            Course t = map.get(to);

            // 将t的入度加1
            t.in++;
            // 将t加入到f的nexts集合中
            f.nexts.add(t);
        }
        // 至此,构造完了拓扑序列的结构

        // 入度为0的队列,用来做拓扑序列的遍历
        // 只有入度为0的课程,才能保证这个课程所需要的所有前置课程都学完了,才可以遍历到这个入度为0的课程上
        // 我们只能遍历从队列中弹出的入读为0的课程节点,这样才能保证遍历到的课程是可以进行学习的
        Queue<Course> zeroInQueue = new LinkedList<>();
        // 标记ans记录到哪里了
        int index = 0;
        // 找到拓扑序列中入度为0的节点,将其加入到队列中,这个节点就是拓扑序列的起点
        // 遍历0~numCourses-1全部的课程,如果这个课程不存在学习的依赖关系,直接加入ans;如果有依赖关系,就先去进行拓扑排序
        for (int i = 0; i < numCourses; i++) {
            // i不在map中,说明不存在依赖关系
            if (!map.containsKey(i)) {
                // 直接加入答案
                ans[index++] = i;
            } else {
                if (map.get(i).in == 0) {
                    zeroInQueue.add(map.get(i));
                }
            }
        }

        // 记录在拓扑序列遍历了多少个节点了,即已经学完了多少个课程了
        int cnt = 0;
        // 开始遍历拓扑序列
        while (!zeroInQueue.isEmpty()) {
            // 弹出队列头部,cur为当前遍历到的节点
            Course cur = zeroInQueue.poll();
            // 弹出的就加入到ans中,也就可以输出拓扑排序
            ans[index++] = cur.courseId;
            // cur的入度为0,说明他需要的前置课程都已经学完了,所以可以学cur这门课了
            // 将已经学完的课程数加1
            cnt++;
            // 遍历cur课程所有的nexts集合中的课程,cur作为这些课程所需要的前置课程
            for (Course nextCourse : cur.nexts) {
                // 将每一个nextCourse的入度减1,表示学完cur后,nextCourse所需要的前置课程数少了一个
                // 如果nextCourse课程的入度减1之后为0了,说明nextCourse这个可能所需要的前置课程也都学完了,将其加入到队列中,用于后续对其遍历
                if (--nextCourse.in == 0) {
                    zeroInQueue.add(nextCourse);
                }
            }
        }
        // map.size()表示全部的课程数
        // 如果最多可以学完的课程数cnt 等于 map.size(),就说明我们学完了所有课程,返回true
        return cnt == map.size() ? ans : new int[] {};
    }
}

三、解题思路 

如果它真的能排序的话,请你输出拓扑排序的顺序。从zerolnQueue里弹出一个收集一个,把每一个沿途出队列的顺序都收集好就是拓扑排序的顺序。

注意这道题可能存在没有学习依赖关系的课程,这种课程可以直接学,直接加入到结果数组中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值