leetcode:207. 课程表

题目

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

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

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

示例 1:

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

示例 2:

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

提示:

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= 5000
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • prerequisites[i] 中的所有课程对 互不相同

课程表问题的实现思路

这个问题可以看作是一个有向图的拓扑排序问题。我们需要判断在给定的课程和先修课程关系下,是否可以完成所有课程的学习。具体思路如下:

  1. 图的表示:我们将课程视为图中的节点,先修关系视为有向边。例如,如果课程 A 需要先修课程 B,则有一条从 B 指向 A 的边。
  2. 入度计算:我们需要计算每个课程的入度(即有多少课程指向它)。如果一个课程的入度为 0,说明可以直接学习。
  3. 拓扑排序:使用队列来实现拓扑排序。将所有入度为 0 的课程加入队列,然后逐一学习这些课程,并减少其后续课程的入度。如果某个后续课程的入度减为 0,则将其加入队列。
  4. 判断完成情况:如果我们能够学习的课程数量等于总课程数量,则返回 true,否则返回 false

C++ 实现

以下是使用 C++ 实现的代码:

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
    vector<int> inDegree(numCourses, 0); // 记录每个课程的入度
    vector<vector<int>> graph(numCourses); // 邻接表表示图

    // 构建图和入度数组
    for (const auto& prereq : prerequisites) {
        int course = prereq[0];
        int preCourse = prereq[1];
        graph[preCourse].push_back(course); // preCourse -> course
        inDegree[course]++;
    }

    // 初始化队列,将所有入度为0的课程加入队列
    queue<int> q;
    for (int i = 0; i < numCourses; i++) {
        if (inDegree[i] == 0) {
            q.push(i);
        }
    }

    int count = 0; // 记录可以学习的课程数量

    // 拓扑排序
    while (!q.empty()) {
        int course = q.front();
        q.pop();
        count++;

        // 遍历所有依赖于该课程的后续课程
        for (int nextCourse : graph[course]) {
            inDegree[nextCourse]--; // 减少后续课程的入度
            if (inDegree[nextCourse] == 0) {
                q.push(nextCourse); // 如果入度为0,加入队列
            }
        }
    }

    // 判断是否可以完成所有课程
    return count == numCourses;
}

int main() {
    int numCourses = 2;
    vector<vector<int>> prerequisites = {{1, 0}};
    cout << (canFinish(numCourses, prerequisites) ? "true" : "false") << endl;

    numCourses = 2;
    prerequisites = {{1, 0}, {0, 1}};
    cout << (canFinish(numCourses, prerequisites) ? "true" : "false") << endl;

    return 0;
}

代码说明

  1. 图和入度数组的构建:通过遍历 prerequisites 数组,构建图的邻接表和入度数组。
  2. 队列的使用:使用队列来处理所有入度为 0 的课程,进行拓扑排序。
  3. 课程计数:在拓扑排序过程中,记录可以学习的课程数量,最后与总课程数量进行比较。

复杂度

这道题的时间复杂度为 O(V+E),其中 V 表示课程数量,E 表示先修课程的数量。具体分析如下:

  1. 构建图和入度数组:遍历所有的先修课程关系,需要 O(E) 的时间。
  2. 拓扑排序
    • 初始化队列,将所有入度为 0 的课程加入队列,需要 O(V) 的时间。
    • 从队列中取出课程并减少其后续课程的入度,每个课程最多进出队列一次,因此需要 O(V+E) 的时间。
  3. 判断是否可以完成所有课程:只需要比较学习的课程数量与总课程数量,时间复杂度为 O(1)。

综上所述,总的时间复杂度为 O(V+E)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值