Course Schedule II

Medium Top 250
Associated Patterns
Interviewed At (Company Tags)
AmazonGoogleMicrosoftMetaApple

Problem Description

There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai.

  • For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1.

Return the ordering of courses you should take to finish all courses. If there are many valid answers, return any of them. If it is impossible to finish all courses, return an empty array.


Examples

Example 1:

Input: numCourses = 2, prerequisites = [[1,0]] Output: [0,1] Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1].

Example 2:

Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] Output: [0,2,1,3] Explanation: There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3].

Example 3:

Input: numCourses = 1, prerequisites = [] Output: [0]


Constraints

  • 1 ≤ numCourses ≤ 2000
  • 0 ≤ prerequisites.length ≤ numCourses * (numCourses - 1)
  • prerequisites[i].length == 2
  • 0 ≤ ai, bi < numCourses
  • ai != bi
  • All the pairs [ai, bi] are distinct

Kahn’s Algorithm: BFS Topological Sort

To find the ordering of a directed acyclic graph (DAG), compute the in-degree of each node (how many prerequisites it has). Seed a queue with all nodes having an in-degree of 0 (no prerequisites).

Process nodes level-by-level:

  1. Pop a course from the queue and add it to the ordering.
  2. For each neighbor (courses that depend on this one), decrement its in-degree.
  3. If a neighbor’s in-degree drops to 0, push it to the queue.

If the final ordering length is less than numCourses, there is a cycle (e.g., A depends on B and B depends on A), meaning it’s impossible to take all courses. Return an empty array.


Solution: Kahn’s Algorithm (BFS)

import java.util.*;

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        List<List<Integer>> adj = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) adj.add(new ArrayList<>());
        int[] inDegree = new int[numCourses];

        for (int[] pre : prerequisites) {
            adj.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.offer(i);
        }

        int[] order = new int[numCourses];
        int idx = 0;

        while (!queue.isEmpty()) {
            int curr = queue.poll();
            order[idx++] = curr;

            for (int neighbor : adj.get(curr)) {
                inDegree[neighbor]--;
                if (inDegree[neighbor] == 0) {
                    queue.offer(neighbor);
                }
            }
        }

        return idx == numCourses ? order : new int[0];
    }
}
from collections import deque

class Solution:
    def findOrder(self, numCourses: int, prerequisites: list[list[int]]) -> list[int]:
        adj = [[] for _ in range(numCourses)]
        in_degree = [0] * numCourses

        for dest, src in prerequisites:
            adj[src].append(dest)
            in_degree[dest] += 1

        queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
        order = []

        while queue:
            curr = queue.popleft()
            order.append(curr)
            for neighbor in adj[curr]:
                in_degree[neighbor] -= 1
                if in_degree[neighbor] == 0:
                    queue.append(neighbor)

        return order if len(order) == numCourses else []
#include <vector>
#include <queue>

class Solution {
public:
    std::vector<int> findOrder(int numCourses, std::vector<std::vector<int>>& prerequisites) {
        std::vector<std::vector<int>> adj(numCourses);
        std::vector<int> inDegree(numCourses, 0);

        for (const auto& pre : prerequisites) {
            adj[pre[1]].push_back(pre[0]);
            inDegree[pre[0]]++;
        }

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

        std::vector<int> order;
        while (!q.empty()) {
            int curr = q.front(); q.pop();
            order.push_back(curr);

            for (int neighbor : adj[curr]) {
                if (--inDegree[neighbor] == 0) {
                    q.push(neighbor);
                }
            }
        }

        return order.size() == numCourses ? order : std::vector<int>();
    }
};

Complexity Analysis:

  • Time Complexity: O(V + E) where V is numCourses and E is the number of prerequisites. We check every node and edge once.
  • Space Complexity: O(V + E) to store the adjacency list representation of the graph.

Where it breaks: returning the wrong base case when it is impossible. Double check that you return an empty array/vector instead of a partial schedule or null.


Common Mistakes

  • Incorrect adjacency representation direction. Make sure the edge goes from prerequisite to the course depending on it: pre[1] -> pre[0]. If you draw the edges backwards, you’ll need to reverse the result or run a post-order traversal.
  • Forgetting to check for cycles. If a cycle exists, Kahn’s algorithm leaves some nodes with in-degrees > 0, so they never enter the queue, making the output array length less than numCourses.

← All Problems