150NeetCode 150

87.Course Schedule

MediumBlind 75Graphs ยท 8/19

Problem

There are numCourses courses labelled 0 to numCourses - 1. You are given prerequisites, where prerequisites[i] = [a, b] means you must take course b before course a.

Return true if it is possible to finish all courses, otherwise false.

Examples

Input:  numCourses = 2, prerequisites = [[1,0]]
Output: true
Explanation: Take course 0, then course 1.
Input:  numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Explanation: Course 1 needs 0 and course 0 needs 1 โ€” a cycle, so neither can ever be taken.
Input:  numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output: true
Explanation: One valid order is 0, 1, 2, 3.

Constraints

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= 5000
  • prerequisites[i].length == 2
  • 0 <= a, b < numCourses
  • All pairs prerequisites[i] are unique

๐Ÿ’ก Key Insight

Model courses as nodes and each pair [a, b] as a directed edge b โ†’ a. All courses can be finished if and only if this directed graph has no cycle โ€” a cycle means every course in it waits on another course in it. Cycle detection in a directed graph takes O(V + E) with either DFS three-colouring (a back edge to a node "in progress" is a cycle) or Kahn's algorithm (repeatedly remove nodes with in-degree 0; if some remain, they sit on a cycle).

Brute Force (Repeated Elimination Rounds)

TimeO(Vยท(V + E))SpaceO(V)

Intuition

Simulate semesters. In each round, take every course whose prerequisites have all already been taken. If a round makes no progress while courses remain, the remaining courses wait on each other forever. Each round rescans every prerequisite pair, which is where the extra factor of V comes from.

Algorithm

  1. taken[] all false, count = 0.
  2. Repeat:
    • Mark blocked[a] = true for every pair [a, b] where b is not yet taken.
    • Take every course that is neither taken nor blocked; increment count.
    • If nothing was taken this round, stop.
  3. Return count == numCourses.

Visualization

numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]

round 1: blocked = {1,2,3} โ†’ take {0}        count = 1
round 2: blocked = {3}     โ†’ take {1,2}      count = 3
round 3: blocked = {}      โ†’ take {3}        count = 4
round 4: nothing left      โ†’ stop            4 == 4 โ†’ true

With [[1,0],[0,1]]: round 1 blocked = {0,1} โ†’ nothing taken โ†’ false

Code

java
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        boolean[] taken = new boolean[numCourses];
        int count = 0;

        while (true) {
            // A course is blocked if any of its prerequisites is not yet taken
            boolean[] blocked = new boolean[numCourses];
            for (int[] p : prerequisites) {
                if (!taken[p[1]]) blocked[p[0]] = true;
            }
            boolean progress = false;
            for (int course = 0; course < numCourses; course++) {
                if (!taken[course] && !blocked[course]) {
                    taken[course] = true;
                    count++;
                    progress = true;
                }
            }
            if (!progress) break; // no course is available this round
        }
        return count == numCourses;
    }
}

Complexity

  • Time: O(Vยท(V + E)) โ€” up to V rounds (a chain of courses), each scanning all E pairs and V courses.
  • Space: O(V) โ€” the taken and blocked arrays.

Complexity Comparison

Approach Time Space
Repeated Elimination Rounds O(Vยท(V + E)) O(V)
DFS (Three Colours) O(V + E) O(V + E)
Kahn's Topological Sort O(V + E) O(V + E)

Common Pitfalls

  • Using a single visited boolean for DFS โ€” it cannot distinguish "on the current path" (cycle) from "finished earlier via another branch" (fine), so diamonds like 0โ†’1โ†’3, 0โ†’2โ†’3 get reported as cycles.
  • Forgetting to set the state to "done" after exploring a node, which makes the DFS exponential on dense DAGs.
  • Reversing the edge direction inconsistently โ€” for cycle detection either direction works, but building graph[a].add(b) while counting indegree[a] breaks Kahn's algorithm.
  • Assuming every course appears in prerequisites; courses with no pairs at all have in-degree 0 and must still be counted.
  • Starting DFS only from course 0 โ€” the graph may be disconnected, so every unvisited course must be a starting point.