题目
地址: https://leetcode.com/problems/course-schedule/description/
类别: Topological Sort
难度: Medium
描述:
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.
分析
给定n个课程(0~n-1)供学生学习, 学生学习课程有先后限制,限制[i,j]表示学习课程i之前必须学习完课程j。
现给出n个课程及课程之间的限制关系,判断学生是否可以按照某一学习顺序学习完所有课程。
例:
Input:
课程数:3
限制:[[1,0],[2,1]]
Output:
true(0->1->2)
Input:
课程数:3
限制:[[1,0],[2,1],[0,2]]
Output:
false
不难发现输入可以转化为有向图,而转化后的图中只要有环,必定存在不能满足的依赖,即环中的课程都相互依赖导致无法学习,最后无法学完所有课程。因此原问题可转化为判断等价有向图中是否存在环。
思路
方法:拓扑排序
目标:判断有向图是否可拓扑排序(是否无环)。
为了便于拓扑排序时的操作,我们先将原输入转化为邻接链表:
vector<unordered_set<int>> convertToAdjacencyLists(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> AdjacencyLists(numCourses);
for(auto pre : prerequisites)
AdjacencyLists[pre.first].insert(pre.second);
return AdjacencyLists;
}
然后计算每个顶点的入度:
vector<int> computeIndegree(int numCourses, vector<unordered_set<int>>& AdjacencyLists) {
vector<int> inDegrees(numCourses, 0);
for(auto outNeighbors : AdjacencyLists)
for(auto n : outNeighbors)
inDegrees[n]++;
return inDegrees;
}
最后进行拓扑排序判断:
1. 若有向图中不存在入度为0的顶点,则必定存在环,返回false;否则任意选择一个入度为0的顶点q;
2. 从图中删除q点和所有以它为起点的边;
3. 重复1和2至删除掉所有顶点结束或在1的过程中不存在入度为0的顶点中断为止。
代码:
class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> AdjacencyLists = convertToAdjacencyLists(numCourses, prerequisites);
vector<int> inDegrees = computeIndegree(numCourses, AdjacencyLists);
queue<int> Q;
for(int i = 0; i < numCourses; i++)
if(inDegrees[i] == 0)
Q.push(i);
int count = 0;
while(!Q.empty()) {
int q = Q.front();
Q.pop();
count++;
for(auto outNeighbor : AdjacencyLists[q])
if(!(--inDegrees[outNeighbor]))
Q.push(outNeighbor);
}
return (count == numCourses);
}
private:
vector<int> computeIndegree(int numCourses, vector<unordered_set<int>>& AdjacencyLists) {
vector<int> inDegrees(numCourses, 0);
for(auto outNeighbors : AdjacencyLists)
for(auto n : outNeighbors)
inDegrees[n]++;
return inDegrees;
}
vector<unordered_set<int>> convertToAdjacencyLists(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> AdjacencyLists(numCourses);
for(auto pre : prerequisites)
AdjacencyLists[pre.first].insert(pre.second);
return AdjacencyLists;
}
};
简化:
如果忽略模块化的话,可以将在建立邻接链表的同时求出入度
class Solution {
public:
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<int> inDegrees(numCourses, 0);
vector<unordered_set<int>> AdjacencyLists = convertToAdjacencyLists(numCourses, prerequisites, inDegrees);
queue<int> Q;
for(int i = 0; i < numCourses; i++)
if(inDegrees[i] == 0)
Q.push(i);
int count = 0;
while(!Q.empty()) {
int q = Q.front();
Q.pop();
count++;
for(auto outNeighbor : AdjacencyLists[q])
if(!(--inDegrees[outNeighbor]))
Q.push(outNeighbor);
}
return (count == numCourses);
}
private:
vector<unordered_set<int>> convertToAdjacencyLists(int numCourses, vector<pair<int, int>>& prerequisites, vector<int> & inDegrees) {
vector<unordered_set<int>> AdjacencyLists(numCourses);
for(auto pre : prerequisites) {
AdjacencyLists[pre.first].insert(pre.second);
inDegrees[pre.second]++;
}
return AdjacencyLists;
}
};
注:在实现代码的时候用队列或栈保存0入度点都是可以的,因为同时存在在里面的点之间没有关系。