一、题目描述
There are a total of n courses you have to take, labeled from 0
to n - 1
.
Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]
Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?
For example:
2, [[1,0]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible.
2, [[1,0],[0,1]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.
二、我的解法
1、解题思路
一个图中的边可以分为树边、前向边、回边和横边,我们重点关注前向边、回边和横边的性质。如果采用DFS对图进行搜一遍,并且按照访问和返回的顺序,对图中的每个节点进行标号,左边的数字(pre)表示访问的序号,右边的数字(post)表示返回的序号,则一幅图可以表示为以下状态:
其中假定一条边是右v指向u:v—>u,则有如下性质:
回边:pre(v)>pre(u) , post(v)<post(u)
前向边:pre(v)<pre(u) , post(v)>post(u)
横边:pre(v)>pre(u) , post(v)>post(u)
所以根据给出的信息,构建图,然后根据图的结构构建DFS访问后每个节点的pre和post值,最后根据pre和post值检查图中是否含有回边,如果包含回边,则图中有环。
用vector<unordered_set<int>>存储图的信息,用vector<pair<int,int>>存储pre和post值,全局变量count表示访问和返回的顺序,函数dfs表示深度优先遍历图,并对遍历的每个节点标记pre和post。
2、代码实现
class Solution {
public:
int count =0;
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> graph(numCourses);
for(auto p:prerequisites){
graph[p.second].insert(p.first);
}
vector<pair<int,int>> point(numCourses,make_pair(0,0));
for(int i=0;i<numCourses;i++){
dfs(graph,point,i);
}
for(int v=0;v<numCourses;v++)
for(int u:graph[v])
if(point[v].first>point[u].first&&point[v].second<point[u].second)
return false;
return true;
}
void dfs(vector<unordered_set<int>>& graph,vector<pair<int,int>>& point,int node){
if(point[node].first==0){
point[node].first=++count;
for(auto neigh:graph[node])
dfs(graph,point,neigh);
point[node].second=++count;
}
}
};
三、其它解法一
1、解题思路
DFS遍历图,如果在一条搜索路径中,访问了该路径中已经访问过的节点,则说明有环。该方法需要注意的是,要创建两个vector:visited和onpath,其中visited用于记录各条路径中已经访问过的节点,目的是为了避免重复访问节点,已经访问过的就不用在访问了,而onpath是用于记录一条路径中访问过的节点,用于检查是否有环,所以在路径访问结束之后,onpath中的值要清空,代码中visited和onpath都是用true表示访问过,false表示未访问。
2、代码实现
class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> graph(numCourses);
for(auto p:prerequisites)
graph[p.second].insert(p.first);
vector<bool> visited(numCourses,false);
vector<bool> onpath(numCourses,false);
for(int i=0;i<numCourses;i++)
if(!visited[i]&&dfs_circle(graph,i,visited,onpath))
return false;
return true;
}
bool dfs_circle(vector<unordered_set<int>>& graph,int node,vector<bool>& visited,vector<bool>& onpath){
if(visited[node])return false;
onpath[node]=visited[node]=true;
for(auto neigh:graph[node])
if(onpath[neigh]||dfs_circle(graph,neigh,visited,onpath))
return true;
onpath[node] = false;
return false;
}
};
四、其它解法二
1、解题思路
BFS遍历图,并通过compute_indegree函数记录每个节点的入度。然后遍历图,如果没有一个节点的入度为0,则有环,如果有至少一个节点的入度为0,则暂时无环,将入度为0的节点标记下来,其它所有与该入度为0的节点相连的节点的入度减一,直至把所有节点都遍历完。
2、代码实现
class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> graph(numCourses);
for (auto pre : prerequisites)
graph[pre.second].insert(pre.first);
vector<int> degrees = compute_indegree(graph);
for (int i = 0; i < numCourses; i++) {
int j = 0;
for (; j < numCourses; j++)
if (!degrees[j]) break;
if (j == numCourses) return false;
degrees[j] = -1;
for (int neigh : graph[j])
degrees[neigh]--;
}
return true;
}
vector<int> compute_indegree(vector<unordered_set<int>>& graph) {
vector<int> degrees(graph.size(), 0);
for (auto neighbors : graph)
for (int neigh : neighbors)
degrees[neigh]++;
return degrees;
}
};