LeetCode-Course Schedule II

来源:互联网 发布:淘宝民族风女装品牌 编辑:程序博客网 时间:2024/06/04 23:20

算法分析与设计,第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:

  1. The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input prerequisites.
题目看起来挺长的,不过意思其实不难,就是给定一个数n,表明有n个课程,然后给出一个数组,数组中的每个元素都是一个序列对,其中这个序列的两个数字分别代表两门课程,其中第二个数字所代表的课程是第一个数字所代表的课程的先驱课程,也就是说,如果想要选修第1门课程,那么你必须要先要选修第2门课程。然后最后要求给出一个选修的序列表,能够保证在选修课程的以前,把它的先驱课程都先修完;如果不存在这样的序列,就返回空。
这其实就是要返回所给序列的一个拓扑序。所谓拓扑序,指得是,在一个有向无环图中,如果有一条边,从一个节点a指向到另一个节点b,那么在最后所给的序列中,a必须在b的前面。拓扑序只存在在有向无环图中,在一个有环的图中,是不存在拓扑序的,因为环中的节点都是可以互相到达的。拓扑序可以通过深搜遍历来得到,在访问完与这个相关的所有节点后,把这个节点添加到序列的头部,得到的序列就是拓扑序了。
那么,对于这一题,难点在于,它不保证一定存在拓扑序,所以需要额外的添加判断是否有环的判断。
我的判断是这样字做的:
  1. 首先,初始化一个数组visit,长度为n(所给节点数),其中每个元素的值都是0.
  2. 如果visit[i] ==0,那么对i 进行深搜遍历。
  3. 首先,令visit[i] = -1,表明i这个元素正在进行深搜遍历,然后对于i所指向的的每个节点j,
  4. 如果visit[j] == -1,那么表明j也正在进行深搜遍历,也就是有一条从j到i的路径,然而现在发现,i也有到j的路径,那么这样就说明在这个图中存在环,那么就不存在拓扑序,搜索就此停止;
  5. 如果visit[j] == 0,那么说明j这个节点还没有被访问过,那么就对j也进行深搜遍历。
  6. 如果visit[j] == 1,那么说明j这个节点再此之前已经进行过深搜遍历,且在j这节点之后不存在环。
  7. 如果在结束对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)。


原创粉丝点击