https://leetcode.com/problems/course-schedule-ii/description/
其实这一题是Course Schedule I https://leetcode.com/problems/course-schedule/description/ 的延长版。因为那一题我一次过就做出来了暂时我就不放解了。简单来说思想就是通过有向关系去构建图,然后从图中寻找是否存在环。这一题就是如果图中并不存在环,就返回此图的拓扑排序。
关于什么是拓扑排序,请看http://blog.csdn.net/dm_vincent/article/details/7714519 。 事实上也就是一个叶子结点到根节点的一个序列,在这个序列里叶子结点永远在他的父亲节点或者祖上节点的前面。根据这个特性,拓扑排序关于一幅图是可以存在不止一个序列。
这一题的第一步和Course Schedule是一样的,都是构建图。在这里我只是简单的用了HashMap和HashSet的混用来表达节点和边。HashMap的key是节点本身,value是一个HashSet,表示的是指向哪一个节点,也就是边的set。所以先构图。因为拓扑排序是一个从叶子结点到跟节点的序列。所以需要下面两种东西的其中一种
1. 所有的叶子结点,以及可以从叶子结点回溯到根节点的路径
2.或者所有的根节点,以及可以从根节点回溯到叶子结点的路径。
基本上dfs和bfs都可以解决问题。当然,回到这一题来说,解决方案也有很多种。这里给出一种dfs的,和一种bfs的
dfs的基本思路就是从根节点往叶子结点进行一个bottom-up的操作。先回到这一题解释一下根节点和叶子结点分别是什么,根节点就是这门课不是任何一门课的preresiquite, 叶子结点就是这门课没有任何的preresiquite。
我们先从根节点开始往下找进行一个go for bottom的过程,找到环,就返回空集合表示遇到了环,然后我们不停往上返回空集合以示我们遇到了一个错误的情况。当遇到了叶子结点,我们就开始bottom up的过程,我们不停返回当前层级的节点并往list后面加,因为在这个bottom里面,叶子结点是最先被返回并加到结果集合里的,然后儿子节点总是比父亲节点先被返回并且加到父亲节点的前面。至此,拓扑排序就成立了。
因为这个图可能包含不止一个根节点,所以我们可能需要做几次的dfs操作去把所有结果回溯。同时,为了避免重复操作,一旦我们开始返回一个节点并加入到结果里,我们就把它从图中删除,从实际意义上就表示我们已经把这门课给修了,避免以后别的需要这门课作为prerequisite的课再回溯一次这个节点,就没有必要了,因为一旦这门课被回溯到并且加入结果,就表示这门课以及这门课的直接和间接的prerequisites都已经被学过了。具体代码等待会儿讲完bfs一起放出。
bfs的思路就有些相反了。如果说dfs的思路是根据 2 来的(因为可以bottom up), bfs的思路就是根据 1 来的。我们从叶子结点出发,先把所有不需要prerequisite的课都学了(放进queue里),然后看看再能学什么课(检查一下哪些课的prerequisite被全部学完了,再放进queue里)。。如此往下走,直到所有课被学完或者没有课可以再学了。如果所有课都被学完我们就返回结果,否则就表示实际遇到环了,返回空结集。
下面给出代码(包含了bfs和dfs两种):
public int[] findOrder(int numCourses, int[][] prerequisites) {
Map<Integer, Set<Integer>> preReqMap = new HashMap<Integer, Set<Integer>>();
Map<Integer, Set<Integer>> postReqMap = new HashMap<Integer, Set<Integer>>();
for (int[] preq : prerequisites) {
if (!preReqMap.containsKey(preq[0])) {
preReqMap.put(preq[0], new HashSet<Integer>());
}
if (!postReqMap.containsKey(preq[1])) {
postReqMap.put(preq[1], new HashSet<Integer>());
}
preReqMap.get(preq[0]).add(preq[1]);
postReqMap.get(preq[1]).add(preq[0]);
}
// int[] result = new int[numCourses];
// int curIndex = 0;
// Set<Integer> visited = new HashSet<Integer>();
// for (int i = 0; i < numCourses; i++) {
// // case 1 : single node without prerequisite
// if (!preReqMap.containsKey(i) && !postReqMap.containsKey(i)) {
// result[curIndex] = i;
// curIndex++;
// } else if (!postReqMap.containsKey(i)) {
// // case 2: Root node.
// List<Integer> curRes = this._DFSSearchGraph(preReqMap, new HashSet<Integer>(), i);
// if (curRes.isEmpty()) return new int[0];
// for (Integer resCourse : curRes) {
// if (!visited.contains(resCourse)) {
// visited.add(resCourse);
// result[curIndex] = resCourse;
// curIndex++;
// }
// }
// }
// }
// return curIndex == numCourses ? result : new int[0];
return _BFSSearchGraph(preReqMap, postReqMap, numCourses);
}
private int[] _BFSSearchGraph(Map<Integer, Set<Integer>> preReqMap, Map<Integer, Set<Integer>> postReqMap, int num) {
Queue<Integer> canLearn = new LinkedList<Integer>();
int[] res = new int[num];
int curIndex = 0;
for (int i = 0; i < num; i++) {
if (!preReqMap.containsKey(i)) {
canLearn.add(i);
}
}
while (!canLearn.isEmpty()) {
int learnt = canLearn.poll();
res[curIndex] = learnt;
curIndex++;
if (postReqMap.containsKey(learnt)) {
Set<Integer> leadTo = postReqMap.get(learnt);
for (Integer leadCourse : leadTo) {
preReqMap.get(leadCourse).remove(learnt);
if (preReqMap.get(leadCourse).isEmpty()) {
preReqMap.remove(leadCourse);
canLearn.add(leadCourse);
}
}
}
}
return curIndex == num ? res : new int[0];
}
private List<Integer> _DFSSearchGraph(Map<Integer, Set<Integer>> preReqMap, Set<Integer> visited, int course) {
if (visited.contains(course)) {
return new LinkedList<Integer>();
} else {
visited.add(course);
LinkedList<Integer> res = new LinkedList<Integer>();
if (preReqMap.containsKey(course)) {
Set<Integer> reqs = preReqMap.get(course);
for (Integer req : reqs) {
List<Integer> nextRes = _DFSSearchGraph(preReqMap, visited, req);
if (nextRes.isEmpty()) return nextRes;
for (Integer resCourse : nextRes) {
res.add(resCourse);
}
}
preReqMap.remove(course);
}
visited.remove(course);
res.add(course);
return res;
}
}