算法分析与设计,第12周博客
210. Course Schedule II
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, return the ordering of courses you should take to finish all courses.
There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.
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 the correct course order is [0,1]
4, [[1,0],[2,0],[3,1],[3,2]]
There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]
. Another correct ordering is[0,2,1,3]
.
Note:
- The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
- You may assume that there are no duplicate edges in the input prerequisites.
- 首先,初始化一个数组visit,长度为n(所给节点数),其中每个元素的值都是0.
- 如果visit[i] ==0,那么对i 进行深搜遍历。
- 首先,令visit[i] = -1,表明i这个元素正在进行深搜遍历,然后对于i所指向的的每个节点j,
- 如果visit[j] == -1,那么表明j也正在进行深搜遍历,也就是有一条从j到i的路径,然而现在发现,i也有到j的路径,那么这样就说明在这个图中存在环,那么就不存在拓扑序,搜索就此停止;
- 如果visit[j] == 0,那么说明j这个节点还没有被访问过,那么就对j也进行深搜遍历。
- 如果visit[j] == 1,那么说明j这个节点再此之前已经进行过深搜遍历,且在j这节点之后不存在环。
- 如果在结束对i的所有后续节点的遍历且没有提前退出的情况下(也就是i的后续节点中不存在环),那么令visit[i] = 1;并把这个元素放在结果序列的尾部。
bool visit(vector<vector<int>>& g, int s, vector<int>& res, vector<int>& in) {
if (in[s] == 1)
return true;
in[s] = -1;
for (int i = 0; i < g[s].size(); ++i) {
if (in[g[s][i]] == -1)
return false;
if (in[g[s][i]] == 1)
continue;
if (!visit(g, g[s][i], res, in))
return false;
}
res.push_back(s);
in[s] = 1;
return true;
}
另外还有就是图的构建,这里并没有选择邻接矩阵,而是选择了邻接列表,这样可以节省空间的大小,也可以节省一点点的判断时间。另外还有一点需要注意的就是,图中的边是由后续课程指向前驱课程的,并且在添加的时候也是先添加的先驱课程,所以在最后没有对得到的序列进行反转。
class Solution {
public:
vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<vector<int>> g(numCourses);
for (int i = 0; i < prerequisites.size(); ++i) {
pair<int, int> e = prerequisites[i];
g[e.first].push_back(e.second);
}
vector<int> res;
vector<int> in(numCourses, 0); // 0 -> unchecked, 1 -> no circle, -1 -> circle
for (int i = 0; i < g.size(); ++i) {
if (in[i] == 0)
if (!visit(g, i, res, in))
return vector<int>();
}
return res;
}
bool visit(vector<vector<int>>& g, int s, vector<int>& res, vector<int>& in) {
if (in[s] == 1)
return true;
in[s] = -1;
for (int i = 0; i < g[s].size(); ++i) {
if (in[g[s][i]] == -1)
return false;
if (in[g[s][i]] == 1)
continue;
if (!visit(g, g[s][i], res, in))
return false;
}
res.push_back(s);
in[s] = 1;
return true;
}
};
最后来看下这个算法的时间复杂度,设节点数为n,那么基本是需要对一个n*n的一个矩阵做一次深度优先遍历,其中每个节点的访问次数都为1;然后是构建图的时间复杂度,设边的个数为e(因为不存在重边,所以e的上限为O(n^2)),那么每条边需要一个单位的时间;所以,最后的时间复杂度是O(n^2)。