课程表
你这个学期必须选修 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 。这是不可能的。
/**
* @param {number} numCourses
* @param {number[][]} prerequisites
* @return {boolean}
*/
var canFinish = function(numCourses, prerequisites) {
// 构建节点关系 key: 当前节点, value:当前节点指向的所有节点
const edges = buildEdges(prerequisites)
let valid = true
const visited = new Array(numCourses).fill(0)
// 深度遍历,判断是否存在环
function dfs(i) {
const edge = edges[i]
if(!edge) return
visited[i] = 1 // 访问中
for(const nextNode of edge) {
if(visited[nextNode] === 0) {
dfs(nextNode)
if(!valid) return
} else if(visited[nextNode] === 1) {
// 存在环
valid = false;
return
}
}
visited[i] = 2; // 已访问
}
for(let i = 0; i < numCourses; i++) {
if(!visited[i]) {
dfs(i)
}
}
return valid
};
// 构建边的关系
function buildEdges(prerequisites) {
// 根据节点数量,初始化所有的节点的相邻的空数组(用于记录指向的节点)
const edgeMap = {}
for(const [node, preNode] of prerequisites) {
if(!edgeMap[preNode]) {
edgeMap[preNode] = []
}
edgeMap[preNode].push(node)
}
return edgeMap
}
解题思路
核心思路是构建
有向图
,然后深度遍历
判断是否存在环
,存在环
则无法进行拓扑排序
,也说明无法完成所有课程的学习
prerequisites
中,每一项表示[课程, 先修课程]
。遍历prerequisites
转化为key
为先修课程
,value
为课程的数组
。[构建有向图]- 通过
visited数组
记录每个节点的访问状态,0:未访问,1:访问中,2:已访问
,遍历所有节点,进行深度遍历dfs
。 - 深度遍历时,只需要判断,是否在遍历过程中,遇到
访问中的节点
即可,如果遇到访问中的节点
,证明存在环
。(如果不存在环,深度遍历
过程中不可能访问到访问中的节点
)