LeetCode 热题 100_课程表(53_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、本题的要求是在选修某些课程之前需要一些先修课程,且两个课程不能互为先修课程。例子(false):学课程 ai 之前必学 bi,且学课程 bi 之前必学 ai,是不能进行下去的。通过问题的分析,我们发现此问题就是判断当前课程的学习是否构成一个环(有向图),判断环的存在,我们可以使用拓扑排序。在进行拓扑排序后,如果还存在入度为0结点则返回false,否则返回true。
2、具体思路如下:
① 在进行拓扑排序的时候我们需要从入度为0的结点开始,这样我们就要事先统计入度为0的结点,将这些结点加入队列中。
② 将队首结点出队,将此节点后继结点的入度减去(为了方便找到此节点的后继结点我们可以存储此节点和其后继结点的对应关系:邻接表)。
…
…
…
③ 重复上述过程直至队空为止,若此时存在入度>0的结点则返回false,否则返回true(我们也可以统计入度为0结点的数量,最后与总的结点数进行比较,相同则返回true,否则返回false)。
3、复杂度分析:
① 时间复杂度:O(n+m),其中 n 为课程数,m 为先修课程的要求数。首先遍历一遍所有的先修课程,统计每个课程的入度并统计每个课程的后继课程O(m)。将入度为0的课程压入栈中O(n),拓扑排序遍历课程结点O(n)。
② 空间复杂度:O(n+m),其中 n 为课程数,m 为先修课程的要求数。存储每个课程的入度O(n)和存储每个课程的后继课程(邻接表)O(n+m)(m可以理解为图中的边数)。
代码实现
代码实现(思路一(拓扑排序)):
class Solution {
private:
//in_degree存储每个结点的入度(下标代表当前结点)
vector<int> in_degree;
//存储每个结点对应的后继结点:邻接表(下标代表当前结点,与之对应的vector<int>为对应的出边)
vector<vector<int>> thisNode_successors;
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
//Q队列用于存储入度为0的结点
queue<int> Q;
int n=0;
//重置数组的大小,并将入度初始化为0
thisNode_successors.resize(numCourses);
in_degree.resize(numCourses,0);
//统计每个课程的入度并统计每个课程的后继课程
for (const auto & prerequisite : prerequisites)
{
//统计每个课程的入度
in_degree[prerequisite[0]]++;
//统计每个课程的后继课程
thisNode_successors[prerequisite[1]].push_back(prerequisite[0]);
}
//将一开始入度为0的结点入队,并统计入度为0的结点数
for (int i = 0; i < numCourses; i++){
if (in_degree[i]==0) Q.push(i);
++n;
}
//将队首结点出队,将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数
while (!Q.empty())
{
//将队首结点出队
int currentNode= Q.front();
Q.pop();
//将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数
for (auto &successor : thisNode_successors[currentNode])
{
in_degree[successor]--;
if (in_degree[successor]==0)
{
Q.push(successor);
++n;
}
}
}
//将入度为0的结点与总的结点数进行比较,相同则返回true,否则返回false
if(n==numCourses) return true;
return false;
}
};
以思路一为例进行调试
#include<iostream>
#include <vector>
#include <queue>
using namespace std;
class Solution {
private:
//in_degree存储每个结点的入度(下标代表当前结点)
vector<int> in_degree;
//存储每个结点对应的后继结点:邻接表(下标代表当前结点,与之对应的vector<int>为对应的出边)
vector<vector<int>> thisNode_successors;
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
//Q队列用于存储入度为0的结点
queue<int> Q;
int n=0;
//重置数组的大小,并将入度初始化为0
thisNode_successors.resize(numCourses);
in_degree.resize(numCourses,0);
//统计每个课程的入度并统计每个课程的后继课程
for (const auto & prerequisite : prerequisites)
{
//统计每个课程的入度
in_degree[prerequisite[0]]++;
//统计每个课程的后继课程
thisNode_successors[prerequisite[1]].push_back(prerequisite[0]);
}
//将一开始入度为0的结点入队,并统计入度为0的结点数
for (int i = 0; i < numCourses; i++){
if (in_degree[i]==0) Q.push(i);
++n;
}
//将队首结点出队,将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数
while (!Q.empty())
{
//将队首结点出队
int currentNode= Q.front();
Q.pop();
//将此节点后继结点的入度减去,将剩余入度为0的结点入队,并统计入度为0的结点数
for (auto &successor : thisNode_successors[currentNode])
{
in_degree[successor]--;
if (in_degree[successor]==0)
{
Q.push(successor);
++n;
}
}
}
//将入度为0的结点与总的结点数进行比较,相同则返回true,否则返回false
if(n==numCourses) return true;
return false;
}
};
int main(int argc, char const *argv[])
{
//总共有 2 门课程
int numCourses=2;
//两门课程的对应关系
vector<vector<int>> prerequisites={{1,0},{0,1}};
Solution s;
if(s.canFinish(numCourses,prerequisites)){
cout<<"true";
}else{
cout<<"false";
}
return 0;
}
LeetCode 热题 100_课程表(53_207)原题链接
欢迎大家和我沟通交流(✿◠‿◠)