题目描述
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [,
] ,表示如果要学习课程
则 必须 先学习课程
。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
分析
关键的信息在“要学习课程 则 必须 先学习课程
”,这就意味着课程之间存在一些依赖关系,在示例2中,课程0依赖课程1,课程1依赖课程0,出现了循环依赖的情况,因此不能完成学习。看到循环依赖我们很容易想到环,进而想到在有向图中会出现环。于是可以将问题建模为在有向图中找环。这就有两种方法,第一种是拓扑排序,存在拓扑排序的图中一定没有环。第二种是直接找环,这里我们选用第二种方法,直接找环。
解题
主要分为2个步骤1.根据 prerequisites构建邻接表 2.查找环
构建邻接表时,要注意
图中的节点个数为numCourses,即课程数。
prerequisites给的是边信息,prerequisites[i] = [
,
]表示存在一条从
指向
的边,因此
是
的一个邻接节点,其余以此类推。
查找环:在对图的遍历过程种,对于一个节点,其有三种状态,0表示还未遍历,1表示已经遍历结束(所有邻接节点都已遍历),2表示正在遍历(还有邻接节点未遍历),那么如果在遍历状态为1的A节点时,再次遍历到另一个状态为1的B节点,就表示两个节点之间存在环,这并不难理解。我们可以用一个长度为numCourses的int数组来记录每个节点的状态。
具体代码如下:
public boolean canFinish(int numCourses, int[][] prerequisites) {
if (prerequisites.length == 0 || prerequisites.length == 1) return true;
List<List<Integer>> adj = new ArrayList<>();
int[] visited = new int[numCourses];
// 添加节点,节点个数为numCourses
for (int i = 0; i < numCourses; i++) {
adj.add(new ArrayList<>());
}
// 添加邻接节点
for (int i = 0; i < prerequisites.length; i++) {
adj.get(prerequisites[i][0]).add(prerequisites[i][1]);
}
// 遍历图中节点
for (int i = 0; i < numCourses; i++) {
if(findCycle(i, visited, adj)) return false;
}
return true;
}
public boolean findCycle(int node, int[] visited, List<List<Integer>> adj) {
// 0表示还未访问
// 1表示已访问过
// 2表示正在访问
if (visited[node] == 2) return true; // 遍历到某正在访问的节点,找到环,返回true
if (visited[node] == 1) return false; // 遍历到访问结束的节点,未找到环,返回false
// 标记正在遍历的节点
visited[node] = 2;
// 遍历邻接节点
for (Integer next : adj.get(node)) {
if(findCycle(next, visited, adj)) return true; //如果找到环,返回true
}
visited[node] = 1; // 标记访问结束的节点
return false; // 未找到环,返回false
}