现在你总共有 n 门课需要选,记为 0
到 n - 1
.
一些课程在修之前需要先修另外的一些课程,比如要学习课程 0 你需要先学习课程 1 ,表示为[0,1]
- 给定n门课以及他们的先决条件,判断是否可能完成所有课程?
在线评测地址:lintcode领扣
例1:
输入: n = 2, prerequisites = [[1,0]]
输出: true
例2:
输入: n = 2, prerequisites = [[1,0],[0,1]]
输出: false
【题解】
对于两门课之间的约束关系,很容易联想到图,我们可以将课抽象为节点,将约束抽象为一条有向边,可以用有向图的相关算法解决问题。拓扑排序正好可以解决这一问题。
算法:拓扑排序
一个合法的选课序列就是一个拓扑序,拓扑序是指一个满足有向图上,不存在一条边出节点在入节点后的线性序列,如果有向图中有环,就不存在拓扑序。可以通过拓扑排序算法来得到拓扑序,以及判断是否存在环。
拓扑排序步骤:
- 建图并记录所有节点的入度。
- 将所有入度为
0
的节点加入队列。 - 取出队首的元素
now
,将其加入拓扑序列。 - 访问所有
now
的邻接点nxt
,将nxt
的入度减1
,当减到0
后,将nxt
加入队列。 - 重复步骤
3
、4
,直到队列为空。 - 如果拓扑序列个数等于节点数,代表该有向图无环,且存在拓扑序。
复杂度分析
设课程数,即图的节点数为V。
约束数量,即图的边数为E。
时间复杂度O(V + E)
- 建图,扫描一遍所有的边
O(E)
。 - 每个节点最多入队出队
1
次,复杂度O(V)
。 - 邻接表最终会被遍历
1
遍,复杂度O(E)
。 - 综上,总复杂度为
O(E + V)
。
空间复杂度O(V + E)
- 邻接表占用
O(E + V)
的空间。 - 队列最劣情况写占用
O(V)
的空间。 - 综上,总复杂度为
O(V + E)
。
public class Solution {
/*
* @param numCourses: a total of n courses
* @param prerequisites: a list of prerequisite pairs
* @return: true if can finish all courses or false
*/
public boolean canFinish(int numCourses, int[][] prerequisites) {
List[] graph = new ArrayList[numCourses];
int[] inDegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
graph[i] = new ArrayList<Integer>();
}
// 建图
for (int[] edge: prerequisites) {
graph[edge[1]].add(edge[0]);
inDegree[edge[0]]++;
}
int numChoose = 0;
Queue queue = new LinkedList();
// 将入度为 0 的编号加入队列
for(int i = 0; i < inDegree.length; i++){
if (inDegree[i] == 0) {
queue.add(i);
}
}
while (! queue.isEmpty()) {
int nowPos = (int)queue.poll();
numChoose++;
// 将每条边删去,如果入度降为 0,再加入队列
for (int i = 0; i < graph[nowPos].size(); i++) {
int nextPos = (int)graph[nowPos].get(i);
inDegree[nextPos]--;
if (inDegree[nextPos] == 0) {
queue.add(nextPos);
}
}
}
return numChoose == numCourses;
}
}
更多题解参见:leetcode/lintcode题解
网友评论