Course Schedule

medium

Given numCourses courses labeled from 0 to numCourses - 1 and an array prerequisites where prerequisites[i] = [a, b] indicates that you must take course b before course a, determine if it is possible to finish all courses.

A valid course schedule exists if and only if the prerequisite graph contains no cycles. If a cycle exists, it is impossible to complete all courses because of circular dependencies.

Examples

Example 1

  • Input: numCourses = 2, prerequisites = [[1,0]]
  • Output: true
  • Explanation: There are 2 courses. You must take course 0 before course 1. This is possible.

Example 2

  • Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
  • Output: false
  • Explanation: There are 2 courses. Course 0 requires course 1 and course 1 requires course 0. This creates a cycle, so it is impossible.

Constraints

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

Approaches

2 worked solutions ranked from straightforward to optimal — each with intuition, algorithm, complexity, and verified code in C++, Java, Python, and Go.

Approach 1

DFS Cycle Detection

Intuition

We can model the courses and prerequisites as a directed graph and check whether the graph contains a cycle. If a cycle exists, it is impossible to finish all courses. We perform DFS from each unvisited node, using a three-state coloring scheme: unvisited (white), currently in the DFS stack (gray), and fully processed (black). If during DFS we encounter a gray node, a cycle exists. A node is marked black once all its descendants are fully explored without finding a cycle.

Algorithm

  1. Build an adjacency list from the prerequisites array, where an edge from `b` to `a` means course `b` is a prerequisite for course `a`.
  2. Create a state array for each node initialized to "unvisited".
  3. For each unvisited node, start a DFS traversal.
  4. Mark the current node as "visiting" (in progress). For each neighbor, if it is "visiting", a cycle is detected -- return false. If it is "unvisited", recurse on it.
  5. After processing all neighbors, mark the current node as "visited" (complete).
  6. If no cycle is detected after processing all nodes, return true.
TimeO(V + E) where V is the number of courses and E is the number of prerequisitesSpaceO(V + E) for the adjacency list and recursion stack

Code (C++ · Java · Python · Go)

C++17
class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> graph(numCourses);
        for (auto& pre : prerequisites) {
            graph[pre[1]].push_back(pre[0]);
        }

        // 0 = unvisited, 1 = visiting, 2 = visited
        vector<int> state(numCourses, 0);

        for (int i = 0; i < numCourses; i++) {
            if (state[i] == 0 && hasCycle(graph, state, i)) {
                return false;
            }
        }

        return true;
    }

private:
    bool hasCycle(vector<vector<int>>& graph, vector<int>& state, int node) {
        state[node] = 1;

        for (int neighbor : graph[node]) {
            if (state[neighbor] == 1) return true;
            if (state[neighbor] == 0 && hasCycle(graph, state, neighbor)) return true;
        }

        state[node] = 2;
        return false;
    }
};
Java 21
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            graph.add(new ArrayList<>());
        }
        for (int[] pre : prerequisites) {
            graph.get(pre[1]).add(pre[0]);
        }

        // 0 = unvisited, 1 = visiting, 2 = visited
        int[] state = new int[numCourses];

        for (int i = 0; i < numCourses; i++) {
            if (state[i] == 0 && hasCycle(graph, state, i)) {
                return false;
            }
        }

        return true;
    }

    private boolean hasCycle(List<List<Integer>> graph, int[] state, int node) {
        state[node] = 1;

        for (int neighbor : graph.get(node)) {
            if (state[neighbor] == 1) return true;
            if (state[neighbor] == 0 && hasCycle(graph, state, neighbor)) return true;
        }

        state[node] = 2;
        return false;
    }
}
Python 3.12
from typing import List


class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        graph = [[] for _ in range(numCourses)]
        for a, b in prerequisites:
            graph[b].append(a)

        # 0 = unvisited, 1 = visiting, 2 = visited
        state = [0] * numCourses

        def has_cycle(node):
            state[node] = 1

            for neighbor in graph[node]:
                if state[neighbor] == 1:
                    return True
                if state[neighbor] == 0 and has_cycle(neighbor):
                    return True

            state[node] = 2
            return False

        for i in range(numCourses):
            if state[i] == 0 and has_cycle(i):
                return False

        return True
Go 1.26
func canFinish(numCourses int, prerequisites [][]int) bool {
    graph := make([][]int, numCourses)
    for i := range graph {
        graph[i] = []int{}
    }
    for _, pre := range prerequisites {
        graph[pre[1]] = append(graph[pre[1]], pre[0])
    }

    // 0 = unvisited, 1 = visiting, 2 = visited
    state := make([]int, numCourses)

    var hasCycle func(int) bool
    hasCycle = func(node int) bool {
        state[node] = 1

        for _, neighbor := range graph[node] {
            if state[neighbor] == 1 {
                return true
            }
            if state[neighbor] == 0 && hasCycle(neighbor) {
                return true
            }
        }

        state[node] = 2
        return false
    }

    for i := 0; i < numCourses; i++ {
        if state[i] == 0 && hasCycle(i) {
            return false
        }
    }

    return true
}
Approach 2

BFS Topological Sort (Kahn's Algorithm)

Intuition

We can use Kahn's algorithm to attempt a topological sort of the course graph. We compute the in-degree of each node and start by enqueuing all nodes with in-degree zero (courses with no prerequisites). We process nodes from the queue, decrementing the in-degree of each neighbor. When a neighbor's in-degree reaches zero, it is added to the queue. If we process all nodes, no cycle exists. If some nodes remain unprocessed, a cycle prevents completion.

Algorithm

  1. Build an adjacency list and compute the in-degree of each node.
  2. Add all nodes with in-degree 0 to a queue.
  3. While the queue is not empty, dequeue a node and increment a counter of processed nodes.
  4. For each neighbor of the dequeued node, decrement its in-degree. If the in-degree reaches 0, enqueue it.
  5. After the queue is empty, if the processed count equals `numCourses`, return true. Otherwise, return false.
TimeO(V + E) where V is the number of courses and E is the number of prerequisitesSpaceO(V + E) for the adjacency list, in-degree array, and queue

Code (C++ · Java · Python · Go)

C++17
class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> graph(numCourses);
        vector<int> indegree(numCourses, 0);

        for (auto& pre : prerequisites) {
            graph[pre[1]].push_back(pre[0]);
            indegree[pre[0]]++;
        }

        queue<int> q;
        for (int i = 0; i < numCourses; i++) {
            if (indegree[i] == 0) q.push(i);
        }

        int count = 0;
        while (!q.empty()) {
            int node = q.front();
            q.pop();
            count++;

            for (int neighbor : graph[node]) {
                indegree[neighbor]--;
                if (indegree[neighbor] == 0) {
                    q.push(neighbor);
                }
            }
        }

        return count == numCourses;
    }
};
Java 21
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<List<Integer>> graph = new ArrayList<>();
        int[] indegree = new int[numCourses];

        for (int i = 0; i < numCourses; i++) {
            graph.add(new ArrayList<>());
        }

        for (int[] pre : prerequisites) {
            graph.get(pre[1]).add(pre[0]);
            indegree[pre[0]]++;
        }

        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (indegree[i] == 0) queue.add(i);
        }

        int count = 0;
        while (!queue.isEmpty()) {
            int node = queue.poll();
            count++;

            for (int neighbor : graph.get(node)) {
                indegree[neighbor]--;
                if (indegree[neighbor] == 0) {
                    queue.add(neighbor);
                }
            }
        }

        return count == numCourses;
    }
}
Python 3.12
from typing import List
from collections import deque


class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        graph = [[] for _ in range(numCourses)]
        indegree = [0] * numCourses

        for a, b in prerequisites:
            graph[b].append(a)
            indegree[a] += 1

        queue = deque(i for i in range(numCourses) if indegree[i] == 0)
        count = 0

        while queue:
            node = queue.popleft()
            count += 1

            for neighbor in graph[node]:
                indegree[neighbor] -= 1
                if indegree[neighbor] == 0:
                    queue.append(neighbor)

        return count == numCourses
Go 1.26
func canFinish(numCourses int, prerequisites [][]int) bool {
    graph := make([][]int, numCourses)
    indegree := make([]int, numCourses)

    for i := range graph {
        graph[i] = []int{}
    }

    for _, pre := range prerequisites {
        graph[pre[1]] = append(graph[pre[1]], pre[0])
        indegree[pre[0]]++
    }

    queue := []int{}
    for i := 0; i < numCourses; i++ {
        if indegree[i] == 0 {
            queue = append(queue, i)
        }
    }

    count := 0
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        count++

        for _, neighbor := range graph[node] {
            indegree[neighbor]--
            if indegree[neighbor] == 0 {
                queue = append(queue, neighbor)
            }
        }
    }

    return count == numCourses
}

Frequently asked questions

What is the optimal time complexity of Course Schedule?

The most efficient approach in this guide ("BFS Topological Sort (Kahn's Algorithm)") runs in O(V + E) where V is the number of courses and E is the number of prerequisites time and uses O(V + E) for the adjacency list, in-degree array, and queue extra space. Earlier brute-force approaches are slower; see all 2 approaches above for the full progression.

What pattern does Course Schedule use?

Course Schedule is a medium-level Graphs problem involving Graph, Depth-First Search, Topological Sort. Recognizing the pattern is half the work — see the intuition section above for how to spot it.

Can Course Schedule be solved without extra space?

The most space-efficient approach in this guide uses O(V + E) for the adjacency list, in-degree array, and queue extra space (excluding the input). If you're aiming for in-place — see the trade-off in the algorithm section above — the brute-force approach uses O(V + E) for the adjacency list and recursion stack, while the optimized version uses O(V + E) for the adjacency list, in-degree array, and queue.

Which programming languages does this Ratta solution support?

Every approach above ships with verified, runnable solutions in C++, Java, Python, and Go. The starter templates in the workspace match the same four languages so you can practice in your interview language of choice.

Original problem: leetcode.com

Loading editor...
numCourses=
2
prerequisites=
[[1,0]]
true