https://leetcode.com/problems/course-schedule/
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.
Note:
The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
- This problem is equivalent to finding if a cycle exists in a directed graph. If a cycle exists, no topological ordering exists and therefore it will be impossible to take all courses.
- Topological Sort via DFS - A great video tutorial (21 minutes) on Coursera explaining the basic concepts of Topological Sort.
- Topological sort could also be done via BFS.
解题思路:
本题看上去就很像图的题目,但是又略有不同。因为这个图,是有向的。一个[1,0]的edge,表示上1之前必须上0,实际上就是一个由顶点0指向顶点1的edge。
我们可以观察,对于课程x,选择它,就必须先上n门课,那么x的入度就是n。相反,它是选择m门课的前提,那么x的出度就是m。
这道题的问题,能不能finish all courses的充要条件其实是,这个有向图有无环。
下面是一个BFS的解法。
1. 统计出每门课的入度。
2. 将入度为0,也就是可以随意先上的课,加入到队列中。count++。
3. 取出队列头的课程y,对于它的每个出度课程x,将其入度减一。可以理解为因为y已经上过了,所以x的前置课程少了一门。
如果此时x的入度已经为0,则将x加入队列。count++。
如果x入度仍不为0,代表x还有前置课程没上,啥也不做。
4. 不断执行3,直到队列为空。
5. 看count是不是等于所有课程数量,等于代表所有课程都上过了,返回true,否则返回false。
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { Queue<Integer> queue = new LinkedList<Integer>(); // 课程index的前置课程数量 int[] preNum = new int[numCourses]; int count = 0; for(int[] pair : prerequisites) { preNum[pair[0]]++; } for(int i = 0; i < numCourses; i++) { if(preNum[i] == 0) { queue.offer(i); count++; } } while(queue.size() > 0) { int num = queue.poll(); for(int[] pair : prerequisites) { if(pair[1] == num) { preNum[pair[0]]--;//上了一节前置课程 if(preNum[pair[0]] == 0) {//只有当前置课程全部上了,该才能上该课程 queue.offer(pair[0]); count++; } } } } return count == numCourses; } }
下面是一个DFS的解法,与BFS不同,DFS建立的map是看每个节点的出度。
L ← Empty list that will contain the sorted nodes while there are unmarked nodes do select an unmarked node n visit(n) function visit(node n) if n has a temporary mark then stop (not a DAG) if n is not marked (i.e. has not been visited yet) then mark n temporarily for each node m with an edge from n to m do visit(m) mark n permanently unmark n temporarily add n to head of L
上面是wiki的伪代码,L是最后的拓扑排序结果
1. 对于所有的节点,调用dfs方法。
2. 对于当前节点n,如果n有一个暂时标记,证明n正在被遍历,有回环,停止,不可排序。
3. 如果n啥标记没,证明还没被遍历过,那么标记n为暂时,正在遍历。
4. 对于n的所有出度,循环执行2-3步。
5. 遍历完n的所有出度后,将n标记为已遍历,取消正在遍历的标志。并将n加入排序结果L。
public class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { int[] visited = new int[numCourses]; Map<Integer, List<Integer>> map = new HashMap<Integer, List<Integer>>(); //map记录的是key的出度 for(int[] pair : prerequisites) { if(map.get(pair[1]) == null) { List<Integer> res = new ArrayList<Integer>(); res.add(pair[0]); map.put(pair[1], res); } else { List<Integer> res = map.get(pair[1]); res.add(pair[0]); } } for(int i = 0; i < numCourses; i++) { //有一个顶点不能被遍历到,就返回false if(!dfs(map, visited, i)) { return false; } } return true; } public boolean dfs(Map<Integer, List<Integer>> map, int[] visited, int step) { // -1 已经遍历过,0为遍历过,1正在被遍历 //下一顶点仍然在被遍历,有环了,就返回false if(visited[step] == 1) { return false; } //下一顶点已经遍历过 if(visited[step] == -1) { return true; } List<Integer> list = map.get(step); visited[step] = 1; if(list == null) { visited[step] = -1; return true; } for(Integer next : list) { // 有一个顶点不可达(环),就返回false if(!dfs(map, visited, next)) { return false; } } //标记当前顶点为已遍历过 visited[step] = -1; return true; } }
http://blog.csdn.net/dm_vincent/article/details/7714519
http://www.programcreek.com/2014/05/leetcode-course-schedule-java/
https://en.wikipedia.org/wiki/Topological_sorting
// 20181016
一个简单一点的dfs版本,实际上遍历完成的-1的状态并不是每次都需要置上
class Solution { public boolean canFinish(int numCourses, int[][] prerequisites) { int[] visited = new int[numCourses]; Map<Integer, List<Integer>> nextMap = new HashMap<Integer, List<Integer>>(); for (int[] pair : prerequisites) { if (nextMap.containsKey(pair[1])) { nextMap.get(pair[1]).add(pair[0]); } else { List<Integer> list = new ArrayList<Integer>(); list.add(pair[0]); nextMap.put(pair[1], list); } } for (int i = 0; i < numCourses; i++) { if (!dfs(nextMap, visited, i)) { return false; } } return true; } // visited: // 1 正在被遍历 // 0 还没遍历到 // -1 遍历已经结束 public boolean dfs(Map<Integer, List<Integer>> map, int[] visited, int course) { if (visited[course] == 1) { return false; } else { visited[course] = 1; } if (map.containsKey(course)) { List<Integer> nextCourses = map.get(course); for (int next : nextCourses) { if(!dfs(map, visited, next)) { return false; } } } visited[course] = -1; return true; } }