Q:你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
示例 1:
输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
提示:
输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。(1 <= numCourses <= 10^5)
A:
这个题说那么多,实际上就是判断有没有环。
1.BFS:队列模拟拓扑排序。
public boolean canFinish(int numCourses, int[][] prerequisites) {
if (numCourses <= 1 || prerequisites.length <= 1)
return true;
int[] rudu = new int[numCourses];//存入度
for (int[] prerequisite : prerequisites) {
rudu[prerequisite[1]]++;
}
Queue<Integer> q = new LinkedList<>();//队列里存的是入度为0的点
for (int i = 0; i < numCourses; i++) {
if (rudu[i] == 0)
q.add(i);
}
if (q.isEmpty())
return false;
int count = 0;
while (!q.isEmpty()) {
int temp = q.poll();
count++;
for (int[] prerequisite : prerequisites) {
if (prerequisite[0] == temp) {
int curr = prerequisite[1];
rudu[curr]--;//删除该入度为0的点时,
if (rudu[curr] == 0)
q.add(curr);
}
}
}
return count == numCourses;
}
2.DFS
这里设计一个flag:
- flag = 0,未被读取过;
- flag = -1,当前路径读取,如果当前路径再次读取,说明有环;
- flag = 1,之前被别的路径读取过,不需要再读取了,这步是剪枝的过程。
private int[] flag;
public boolean canFinish(int numCourses, int[][] prerequisites) {
if (numCourses <= 1 || prerequisites.length <= 1)
return true;
flag = new int[numCourses];
Arrays.fill(flag, 0);
for (int[] prerequisite : prerequisites) {
if (flag[prerequisite[0]] == 0) {//未被读取过
boolean f = DFS(prerequisite[0], prerequisites);
if (!f)
return false;
}
}
return true;
}
private boolean DFS(int i, int[][] prerequisites) {
flag[i] = -1;//当前路径被读取
boolean f = true;
for (int[] prerequisite : prerequisites) {
if (prerequisite[0] == i) {
if (flag[prerequisite[1]] == 0) {//从来未被读取
f = DFS(prerequisite[1], prerequisites);
} else if (flag[prerequisite[1]] == -1) {//当前路径又一次被读取
f = false;
}
}
if (!f)
return false;
}
flag[i] = 1;//进入下一次之前,这已经变成前一次的路径了
return true;
}
Q:现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
示例 1:
输入: 2, [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:
输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
说明:
输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
A:
同为拓扑排序。
1.BFS
public int[] findOrder(int numCourses, int[][] prerequisites) {
int[] result = new int[numCourses];
if (numCourses <= 1)
return new int[numCourses];
int[] rudu = new int[numCourses];
for (int[] prerequisite : prerequisites) {
rudu[prerequisite[1]]++;
}
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (rudu[i] == 0)
q.add(i);
}
if (q.isEmpty())
return new int[0];
int count = numCourses-1;
while (!q.isEmpty()) {
int temp = q.poll();
result[count] = temp;
count--;
for (int[] prerequisite : prerequisites) {
if (prerequisite[0] == temp) {
int curr = prerequisite[1];
rudu[curr]--;
if (rudu[curr] == 0)
q.add(curr);
}
}
}
if (count != -1)//注意count需要判断是否为-1
return new int[0];
return result;
}
2.DFS
private int[] flag;
private ArrayList<Integer> res;
public int[] findOrder(int numCourses, int[][] prerequisites) {
if (numCourses <= 1)
return new int[numCourses];
res = new ArrayList<>();
flag = new int[numCourses];
Arrays.fill(flag, 0);
for (int i = 0; i < numCourses; i++) {
if (flag[i] == 0) {//这里和前面不一样,是因为如果有独立的点,一定要放进去,所以每个点都要读
boolean f = DFS(i, prerequisites);
if (!f)
return new int[0];
}
}
int[] result = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
result[i] = res.get(i);
}
return result;
}
private boolean DFS(int i, int[][] prerequisites) {
flag[i] = -1;
boolean f = true;
for (int[] prerequisite : prerequisites) {
if (prerequisite[0] == i) {
if (flag[prerequisite[1]] == 0) {
f = DFS(prerequisite[1], prerequisites);
} else if (flag[prerequisite[1]] == -1) {
f = false;
}
}
if (!f)
return false;
}
flag[i] = 1;
res.add(i);//这样才能逆着放入结果中
return true;
}