Course Schedule
return true if we can finish all the courses
Course Schedule
Problem
There are numCourses courses, numbered from 0 to numCourses - 1. You are given an array prerequisites. Each prerequisites[i] = [a, b] means you must take course b before course a.
Return true if you can finish all courses. Otherwise, return false.
Examples
Example 1
Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
- To take course
1, you must finish course0first. - Take
0, then1. This is possible.
Example 2
Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
- Course
1needs course0, and course0needs course1. - Each course waits for the other, so it is impossible.
Constraints
1 <= numCourses <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= a, b < numCourses- All pairs in
prerequisitesare unique.
Main Idea
Think of each course as a node and each prerequisite as a directed edge b -> a (take b before a).
- If the graph has a cycle, some courses wait for each other forever. The answer is
false. - If the graph has no cycle, we can find a valid order. The answer is
true.
So the problem becomes: does this directed graph have a cycle? This is the same idea as the task order problem, where a topological sort fails when there is a cycle.
Solution 1: Kahn's Algorithm (BFS)
We take courses that have no waiting prerequisites, and finish them one by one.
- In-degree: the number of prerequisites a course still waits for.
- Start with all courses that have in-degree
0. - When a course is finished, lower the in-degree of every course that depends on it.
- If an in-degree becomes
0, that course is ready, so add it to the queue. - At the end, if we finished all courses, there is no cycle.
var canFinish = function (numCourses, prerequisites) {
const graph = Array.from({ length: numCourses }, () => []);
const indegree = new Array(numCourses).fill(0);
// edge b -> a: finish b before a
for (const [a, b] of prerequisites) {
graph[b].push(a);
indegree[a]++;
}
// start with courses that have no prerequisites
const queue = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) queue.push(i);
}
let head = 0; // pointer instead of shift(), so each pop is O(1)
let finished = 0;
while (head < queue.length) {
const course = queue[head++];
finished++;
for (const next of graph[course]) {
indegree[next]--;
// all prerequisites of "next" are done
if (indegree[next] === 0) {
queue.push(next);
}
}
}
// if we finished every course, there is no cycle
return finished === numCourses;
};
How the Code Works
- Step 1:
graph[b]holds the courses that needb.indegree[a]counts the prerequisites ofa. - Step 2: Courses with in-degree
0go into the queue first. - Step 3: For each course taken from the queue, we count it as finished and lower the in-degree of its dependents.
- Step 4: In a cycle, the courses never reach in-degree
0, so they never enter the queue. Thenfinished < numCourses, and we returnfalse.
Dry Run
Let us take numCourses = 2 and prerequisites = [[1,0],[0,1]].
- Edges:
0 -> 1and1 -> 0. - In-degrees:
indegree[0] = 1andindegree[1] = 1. - No course has in-degree
0, so the queue is empty. finished = 0, which is not equal to2. The answer isfalse.
Now let us take prerequisites = [[1,0]].
| Step | Course taken | Update | Queue after | finished |
|---|---|---|---|---|
| Start | - | indegree = [0, 1] | [0] | 0 |
| 1 | 0 | indegree[1]: 1 → 0, so add 1 | [1] | 1 |
| 2 | 1 | no dependents | [] | 2 |
finished = 2 = numCourses, so the answer is true.
Complexity
- Time Complexity:
O(V + E)Vis the number of courses andEis the number of prerequisites.- We build the graph in
O(V + E), and each course and edge is processed once.
- Space Complexity:
O(V + E)- The graph stores
Eedges, and the arrays and queue holdVvalues.
- The graph stores
Solution 2: DFS with Cycle Detection
We walk deep into the graph. We track each course in one of three states:
0: not visited yet.1: in progress, so it is on the current DFS path.2: done, so it and everything after it were checked and are safe.
If we reach a course that is in progress, we came back to the current path. That is a cycle.
var canFinish = function (numCourses, prerequisites) {
const graph = Array.from({ length: numCourses }, () => []);
for (const [a, b] of prerequisites) {
graph[b].push(a);
}
const state = new Array(numCourses).fill(0);
// returns true if a cycle is found
function hasCycle(course) {
if (state[course] === 1) return true; // back on the current path
if (state[course] === 2) return false; // already checked and safe
state[course] = 1; // in progress
for (const next of graph[course]) {
if (hasCycle(next)) return true;
}
state[course] = 2; // done
return false;
}
for (let i = 0; i < numCourses; i++) {
if (hasCycle(i)) return false;
}
return true;
};
How the Code Works
- We start a DFS from every course, because the graph may have separate parts.
- A state of
1means the course is on the current path. Seeing it again means a cycle. - A state of
2means we already checked it, so we skip it. This keeps the time linear. - If any DFS finds a cycle, we return
false. Otherwise, we returntrue.
Complexity
- Time Complexity:
O(V + E)- Each course is fully explored once, and each edge is checked once.
- Space Complexity:
O(V + E)- The graph and the
statearray use this space. - The recursion stack can go as deep as
V, which is at most2000here, so it is safe.
- The graph and the
Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Kahn's (BFS) | O(V + E) | O(V + E) | No recursion |
| DFS with states | O(V + E) | O(V + E) | Recursion depth up to V |
Summary
- Courses and prerequisites form a directed graph. You can finish all courses only if the graph has no cycle.
- Kahn's algorithm repeatedly takes courses with in-degree
0. If it finishes all courses, there is no cycle. - DFS marks courses as not visited, in progress or done. Reaching an in-progress course means a cycle.
- Both solutions run in
O(V + E)time and useO(V + E)space. - The next problem, Course Schedule II, asks for the actual order. The Kahn's solution gives it directly: the order in which courses leave the queue.