【每日一题】力扣207 课程表


题目

题目链接:力扣207:课程表

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 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 <= 10^5
  • 0 <= prerequisites.length <= 5000
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • prerequisites[i] 中的所有课程对 互不相同

解题思路

这题显然是一个 拓扑排序 相关的题,只要检查是否存在环即可,存在即无法完成,不存在就可以完成。

👉递归

递归的思路就是逐个遍历数组里的每个课程,完成一个课程后再去完成下一个可以完成的课程,这里的下一个可以完成的课程可能不止一个,有可能是多个,所以需要遍历完成。如果遍历到已经完成了的课程,那就说明存在环,即返回 false

由于二维数组里的每个数组里存储的第二个数是需要先完成的课程,所以可以先把它的位置调换了(不调换也可以,不过思维是反向的)。然后遍历数组的每个课程到递归函数里,递归函数的参数一定是传入的课程。

函数的返回条件是存在环或者说该课程已经遍历过了。

函数的内容是遍历完成该课程后可以完成的课程,将其传入递归函数。

这里有几个细节的地方是:如样例:[[0,1],[1,2],[2,3],[4,2]] ,完成课程 14 都需要先完成课程 2 ,这时,如果先遍历了课程 2 ,再遍历课程 4 时会被误判成存在环。所以需要一个小处理,把没有遍历到的课程标记为 0 ,刚遍历到的课程标记为 1 ,遍历完当前课程完成后可以完成的课程(如完成了课程 2 后可以完成课程14)标记为2,就可以解决。

👉迭代

迭代我觉得看一下官方的动图就能理解。

主要思路是先从入度最少的节点(课程)作为先完成的课程,然后依次遍历入度多的。可以把节点放在一个队列里,从入度少到多,逐个放入队列进行遍历。具体可以看代码。

代码(C++)

递归

class Solution {
public:
    vector<vector<int>> nums;
    vector<int> vis;
    bool f;
    void dfs(int t) {
        if (vis[t])
            return ;
        vis[t] = 1;
        for (int i = 0; i < nums[t].size(); ++ i) {
            if (!vis[nums[t][i]])
                dfs(nums[t][i]);
            else if (vis[nums[t][i]] == 1) {
                f = true;
                return ;
            }
        }
        vis[t] = 2;
    }

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        nums.resize(numCourses);
        vis.resize(numCourses);
        for (int i = 0; i < prerequisites.size(); ++ i) {
            nums[prerequisites[i][1]].push_back(prerequisites[i][0]);
        }

        for (int i = 0; i < numCourses; ++ i) {
            if (!vis[i])
                dfs(i);
        }
        return !f;
    }
};

迭代

class Solution {
	private:
		vector<vector<int>> edge;
		vector<int> f;
	public:
		bool canFinish(int numCourses, vector<vector<int>> &prerequisites) {
			edge.resize(numCourses);
			f.resize(numCourses);
			for (int i = 0; i < prerequisites.size(); ++i) {
				edge[prerequisites[i][1]].push_back(prerequisites[i][0]);
				f[prerequisites[i][0]]++;
			}
			queue<int> q;
			int visit = 0;
			for (int i = 0; i < numCourses; ++i) {
				if (f[i] == 0)
					q.push(i);
			}
			while (!q.empty()) {
				visit++;
				int u = q.front();
				q.pop();
				for (int j = 0; j < edge[u].size(); ++j) {
					if (--f[edge[u][j]] == 0) {
						q.push(edge[u][j]);
					}
				}
			}
			return visit == numCourses;
		}
}; 

总结

拓扑排序的题不算太少见,应当掌握这类题。这题还算是基本或者经典的拓扑排序题,如果之前没遇到过拓扑排序的题估计不太好想,尤其是一个节点可以连接多个节点而且还不形成环。迭代做法的思路真的很好,从入度少的开始切入,这样简单了很多。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

聆听逝去的流

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值