207. 课程表

207. 课程表

1.题目描述

现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?
示例 1:
在这里插入图片描述
示例 2:
在这里插入图片描述
说明:
1.输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
2.你可以假定输入的先决条件中没有重复的边。
提示:
1.这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
2.通过 DFS 进行拓扑排序 。
3.拓扑排序也可以通过 BFS 完成。

2.方法1(深度优先遍历)

通过 DFS 判断图中是否有环
1.借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:
(1)未被 DFS 访问:i == 0;
(2)被其他节点启动的DFS访问:i == -1;
(3)已被当前节点启动的DFS访问:i == 1。

2.对 numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回 False。DFS 流程:
(1)终止条件:
当flag[i] == -1,说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 True。
当 flag[i] == 1,说明在本轮 DFS 搜索中节点 i 被第 2 次访问,即 课程安排图有环,直接返回 False。
(2)将当前访问节点 i 对应 flag[i] 置 1,即标记其被本轮 DFS 访问过;
(3)递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 False;
(4)当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 -1 并返回 True。
3.若整个图 DFS 结束并未发现环,返回 True。

3.代码

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> inDegree(numCourses);
        for(auto &cp : prerequisites){
            inDegree[cp[1]]++;//记录每个节点的入度
        }
        queue<int> q;
        for(int i = 0;i < numCourses;++i){
            if(inDegree[i] == 0){//把入度为0的节点加入队列
                q.push(i);
            }
        }
        while(!q.empty()){
            int pre = q.front();
            q.pop();
            numCourses--;
            for(auto &req : prerequisites){
                if(req[0] != pre){
                    continue;
                }
                if(--inDegree[req[1]] == 0){//把这个入度为0的节点指向的边减1
                    q.push(req[1]);
                }
            }
        }
        return numCourses == 0;
    }
};

4.复杂度分析

时间复杂度 O(N + M):遍历一个图需要访问所有节点和所有邻边,N 和 M 分别为节点数量和临边数量;
空间复杂度 O(N),为建立邻接矩阵所需额外空间。

5.方法2(拓扑排序)

思路:
1.入度:设有向图中有一结点 v ,其入度即为当前所有从其他结点出发,终点为 v 的边的数目。
2.出度:设有向图中有一结点 v ,其出度即为当前所有起点为 v ,指向其他结点的边的数目。
3.每次从入度为 0 的结点开始,加入队列。入度为 0 ,表示没有前置结点。
4.处理入度为 0 的结点,把这个结点指向的结点的入度 -1 。
5.再把新的入度为 0 的结点加入队列。
6.如果队列都处理完毕,但是和总结点数不符,说明有些结点形成环。

6.代码

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<int> inDegree(numCourses);
        for(auto &cp : prerequisites){
            inDegree[cp[0]]++;//记录每个节点的入度
        }
        queue<int> q;
        for(int i = 0;i < numCourses;++i){
            if(inDegree[i] == 0){//把入度为0的节点加入队列
                q.push(i);
            }
        }
        while(!q.empty()){
            int pre = q.front();
            q.pop();
            numCourses--;
            for(auto &req : prerequisites){
                if(req[1] != pre){
                    continue;
                }
                if(--inDegree[req[0]] == 0){
                    q.push(req[0]);
                }
            }
        }
        return numCourses == 0;
    }
};

7.复杂度分析

时间复杂度:O(N+M),遍历一个图需要访问所有节点和所有临边,N 和 M 分别为节点数量和临边数量;
空间复杂度:O(N),入度表的长度。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值