夯实算法-课程表 II

126 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

题目: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]

提示:

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • ai != bi
  • 所有[ai, bi] 互不相同

解题思路

拓扑排序是一种有向无环图,DFS和BFS通过一些修改都可以用来判断环,不过对于拓扑排序最经典的还是使用BFS的方式构建入度表,来依次遍历各节点,如果最后遍历出来还存在节点入度不为0的节点,也就是遍历不完全则说明一定存在环。 算法步骤:

  • 根据给定的节点连接关系,构建图的邻接表
  • 构建每个节点的入度和出度表
  • 将入度为0的节点存入队列
  • 依次弹出遍历队列中的节点,并更新该节点相邻节点的入度,若出现新增的入度为0的节点则加入队列
  • 记录访问节点的个数cnt,判断节点个数cnt是否与numCourses一致,否则说明该图存在环,无法完成拓扑排序

代码实现

public int[] findOrder(int numCourses, int[][] prerequisites) {
    HashMap < Integer, ArrayList < Integer >> nodemaplist = new HashMap < > ();
    int[] res = new int[numCourses];
    if (numCourses == 0) //特判
        return res;
    else if (numCourses == 1) {
        res[0] = 0;
        return res;
    }

    int[] enter = new int[numCourses]; //入度
    int[] to = new int[numCourses]; //出度
    for (int[] courserelation: prerequisites) {
        int target = courserelation[0];
        int pre = courserelation[1];
        enter[target] ++;
        to[pre] ++;
        if (!nodemaplist.containsKey(pre)) //对先驱节点创建邻接表
            nodemaplist.put(pre, new ArrayList < Integer > ());
        nodemaplist.get(pre).add(target);
    }
    Queue < Integer > queue = new LinkedList < > (); //用队列来维护bfs遍历
    int cnt = 0; //记录访问的节点数
    for (int i = 0; i < enter.length; i++) {
        if (enter[i] == 0)
            queue.offer(i); //初始化队列,将queue加入其中
    }

    while (!queue.isEmpty()) {
        int temp = queue.remove();
        res[cnt++] = temp;
        if (to[temp] == 0) //出度为0则不存在从该节点发出的边,无需遍历
            continue;
        for (int target: nodemaplist.get(temp)) {
            enter[target] --;
            if (enter[target] == 0)
                queue.offer(target);
        }
    }
    if (cnt < numCourses) //cnt比numCousers小则遍历不完全,一定存在环
        return new int[0];
    else
        return res;
}

运行结果

Snipaste_2023-02-08_23-27-55.png

复杂度分析

  • 空间复杂度:O(1)O(1)
  • 时间复杂度:O(n)O(n)

掘金(JUEJIN)  一起分享知识, Keep Learning!