LeetCode题练习与总结:课程表Ⅱ--210

227 篇文章 0 订阅
137 篇文章 0 订阅

一、题目描述

现在你总共有 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] 互不相同

二、解题思路

这个问题可以通过拓扑排序来解决。拓扑排序是针对有向无环图(DAG)的一种排序算法,它会返回一个顶点的线性序列,这个序列满足图中所有的有向边都从序列的前面指向后面。

以下是解题思路:

  1. 创建一个入度数组,用于记录每个顶点的入度(即有多少边指向它)。
  2. 创建一个队列,用于存储所有入度为0的顶点(这些顶点没有先修课程,可以立即学习)。
  3. 当队列非空时,从队列中弹出一个顶点,将该顶点添加到结果列表中,并减少其所有邻接顶点的入度。如果某个邻接顶点的入度变为0,则将其加入队列。
  4. 如果结果列表中的顶点数量等于课程数,则返回结果列表;否则,说明存在环,返回空数组。

三、具体代码

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // 1. 创建入度数组
        int[] inDegree = new int[numCourses];
        // 2. 创建邻接表
        List<List<Integer>> adjList = new ArrayList<>(numCourses);
        for (int i = 0; i < numCourses; i++) {
            adjList.add(new ArrayList<>());
        }
        // 3. 填充入度数组和邻接表
        for (int[] prerequisite : prerequisites) {
            inDegree[prerequisite[0]]++;
            adjList.get(prerequisite[1]).add(prerequisite[0]);
        }
        // 4. 创建队列,存储所有入度为0的顶点
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }
        // 5. 拓扑排序
        int[] order = new int[numCourses];
        int index = 0;
        while (!queue.isEmpty()) {
            int current = queue.poll();
            order[index++] = current;
            for (int next : adjList.get(current)) {
                inDegree[next]--;
                if (inDegree[next] == 0) {
                    queue.offer(next);
                }
            }
        }
        // 6. 检查是否所有课程都被安排了
        if (index == numCourses) {
            return order;
        } else {
            return new int[0]; // 如果存在环,返回空数组
        }
    }
}

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 初始化入度数组和邻接表:我们需要遍历所有课程(numCourses),并初始化每个课程的入度数组和邻接表。这一步的时间复杂度是 O(numCourses)。
  • 填充入度数组和邻接表:我们需要遍历所有的先修课程(prerequisites),对于每个先修课程对,我们需要更新入度数组和邻接表。这一步的时间复杂度是 O(prerequisites.length)。
  • 将所有入度为0的课程加入队列:我们需要再次遍历所有课程,检查每个课程的入度。这一步的时间复杂度是 O(numCourses)。
  • 进行拓扑排序:在拓扑排序过程中,每个课程最多只会被添加到队列一次,并且从队列中移除一次。因此,这一步的时间复杂度是 O(numCourses + prerequisites.length),因为每个先修课程对都会被访问一次。

综上所述,总的时间复杂度是 O(numCourses + prerequisites.length)。

2. 空间复杂度
  • 入度数组:我们创建了一个大小为 numCourses 的数组来存储每个课程的入度。因此,空间复杂度是 O(numCourses)。
  • 邻接表:邻接表中的每个课程最多会有 numCourses - 1 个邻接课程(在最坏的情况下,每个课程都依赖于其他所有课程)。因此,邻接表的空间复杂度是 O(numCourses * (numCourses - 1))。
  • 队列:在最坏的情况下,队列可能需要存储所有课程,因此空间复杂度是 O(numCourses)。
  • 结果数组:结果数组的大小是 numCourses,因此空间复杂度是 O(numCourses)。

将上述空间复杂度相加,我们得到总的空间复杂度是 O(numCourses + numCourses * (numCourses - 1)),即 O(numCourses^2)。在最坏的情况下,这个表达式可以简化为 O(numCourses^2),因为当 numCourses 较大时,numCourses^2 将是主导项。然而,在实际情况下,由于每个课程不可能都依赖于其他所有课程,因此实际的空间复杂度通常会小于这个上限。

五、总结知识点

  1. 数组:代码中使用了int[]类型的数组来存储每个课程的入度,即inDegree数组。

  2. 列表:使用List<List<Integer>>类型的列表来创建邻接表,表示每个课程指向的其他课程。

  3. 队列:使用Queue<Integer>类型的队列来存储所有入度为0的课程,帮助实现拓扑排序。

  4. 循环:代码中使用了for循环来初始化入度数组、邻接表,以及填充这些数据结构。

  5. 条件语句:使用了if语句来检查入度是否为0,并据此将课程加入队列。

  6. 链表LinkedList类被用作队列的实现,它提供了offerpoll方法来添加和移除队列元素。

  7. 拓扑排序:代码的核心是拓扑排序算法,这是一种针对有向无环图(DAG)的排序算法。

  8. 图论:代码解决的是图论中的问题,特别是有向图的拓扑排序问题。

  9. 异常处理:虽然代码中没有显式的异常处理,但返回空数组new int[0]是处理无法完成所有课程(即存在环)的情况。

  10. 数据结构:代码中使用了多种数据结构,包括数组、列表、队列,来存储和处理图的数据。

  11. 算法设计:代码展示了如何设计算法来解决问题,包括如何处理输入数据,执行计算,并返回结果。

  12. 递归的替代:虽然拓扑排序通常可以用递归来实现,但此代码示例使用迭代(循环)方法,避免了递归可能带来的栈溢出问题。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直学习永不止步

谢谢您的鼓励,我会再接再厉的!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值