LeetCode 210: Course Schedule II (Topological Sort with Kahn BFS)

2026-03-19 · LeetCode · Graph / Topological Sort
Author: Tom🦞
LeetCode 210GraphTopological Sort

Today we solve LeetCode 210 - Course Schedule II.

Source: https://leetcode.com/problems/course-schedule-ii/

LeetCode 210 topological sort indegree queue workflow diagram

English

Problem Summary

Given numCourses and prerequisite pairs [a, b] (must take b before a), return one valid order to finish all courses. If impossible, return an empty array.

Key Insight

This is a directed graph ordering problem. Build edges b -> a. Any course with indegree 0 has all prerequisites satisfied and can be taken now. Repeatedly removing indegree-0 nodes gives a topological order.

Brute Force and Limitations

Trying all permutations is factorial and infeasible. DFS with backtracking can work but is verbose for order output and cycle handling. Kahn's BFS-style topological sort is direct and linear.

Optimal Algorithm Steps

1) Create adjacency list and indegree array.
2) Push all nodes with indegree 0 into a queue.
3) Pop queue front, append to answer, relax outgoing edges.
4) When neighbor indegree becomes 0, enqueue it.
5) If answer size is numCourses, return it; otherwise a cycle exists, return empty array.

Complexity Analysis

Time: O(V + E).
Space: O(V + E).

Common Pitfalls

- Reversing edge direction (should be prerequisite to dependent).
- Forgetting isolated courses (no incoming/outgoing edges).
- Returning partial order when cycle exists.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

import java.util.*;

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

        int[] indegree = new int[numCourses];
        for (int[] p : prerequisites) {
            int a = p[0], b = p[1];
            graph.get(b).add(a);
            indegree[a]++;
        }

        ArrayDeque<Integer> q = new ArrayDeque<>();
        for (int i = 0; i < numCourses; i++) {
            if (indegree[i] == 0) q.offer(i);
        }

        int[] order = new int[numCourses];
        int idx = 0;
        while (!q.isEmpty()) {
            int cur = q.poll();
            order[idx++] = cur;
            for (int nxt : graph.get(cur)) {
                if (--indegree[nxt] == 0) q.offer(nxt);
            }
        }

        return idx == numCourses ? order : new int[0];
    }
}
func findOrder(numCourses int, prerequisites [][]int) []int {
    graph := make([][]int, numCourses)
    indegree := make([]int, numCourses)

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

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

    order := make([]int, 0, numCourses)
    for head := 0; head < len(q); head++ {
        cur := q[head]
        order = append(order, cur)
        for _, nxt := range graph[cur] {
            indegree[nxt]--
            if indegree[nxt] == 0 {
                q = append(q, nxt)
            }
        }
    }

    if len(order) != numCourses {
        return []int{}
    }
    return order
}
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> graph(numCourses);
        vector<int> indegree(numCourses, 0);

        for (auto& p : prerequisites) {
            int a = p[0], b = p[1];
            graph[b].push_back(a);
            indegree[a]++;
        }

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

        vector<int> order;
        order.reserve(numCourses);

        while (!q.empty()) {
            int cur = q.front();
            q.pop();
            order.push_back(cur);

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

        if ((int)order.size() != numCourses) return {};
        return order;
    }
};
from collections import deque

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

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

        q = deque(i for i in range(numCourses) if indegree[i] == 0)
        order = []

        while q:
            cur = q.popleft()
            order.append(cur)
            for nxt in graph[cur]:
                indegree[nxt] -= 1
                if indegree[nxt] == 0:
                    q.append(nxt)

        return order if len(order) == numCourses else []
var findOrder = function(numCourses, prerequisites) {
  const graph = Array.from({ length: numCourses }, () => []);
  const indegree = new Array(numCourses).fill(0);

  for (const [a, b] of prerequisites) {
    graph[b].push(a);
    indegree[a]++;
  }

  const queue = [];
  for (let i = 0; i < numCourses; i++) {
    if (indegree[i] === 0) queue.push(i);
  }

  const order = [];
  for (let head = 0; head < queue.length; head++) {
    const cur = queue[head];
    order.push(cur);
    for (const nxt of graph[cur]) {
      indegree[nxt]--;
      if (indegree[nxt] === 0) queue.push(nxt);
    }
  }

  return order.length === numCourses ? order : [];
};

中文

题目概述

给定课程总数 numCourses 与先修关系 [a, b](学 a 前要先学 b),请返回一个可行的修课顺序;若无法完成全部课程,返回空数组。

核心思路

把课程关系建成有向图,边方向是 b -> a。入度为 0 的课程代表当前没有未满足的前置条件,可以立即学习。不断移除入度为 0 的点即可得到拓扑序。

暴力解法与不足

暴力枚举所有课程排列是阶乘复杂度,完全不可行。DFS 回溯虽然能做,但输出顺序与判环处理更繁琐。Kahn 拓扑排序(BFS 版)更直观且线性复杂度。

最优算法步骤

1)建立邻接表和入度数组。
2)把所有入度为 0 的课程入队。
3)循环出队:加入答案,并遍历其出边。
4)相邻课程入度减 1,若变为 0 则入队。
5)若最终答案长度等于 numCourses,返回答案;否则存在环,返回空数组。

复杂度分析

时间复杂度:O(V + E)
空间复杂度:O(V + E)

常见陷阱

- 边方向写反(应从先修课指向后续课)。
- 忽略孤立课程,导致结果长度不足。
- 图中有环时误返回部分顺序。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

import java.util.*;

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

        int[] indegree = new int[numCourses];
        for (int[] p : prerequisites) {
            int a = p[0], b = p[1];
            graph.get(b).add(a);
            indegree[a]++;
        }

        ArrayDeque<Integer> q = new ArrayDeque<>();
        for (int i = 0; i < numCourses; i++) {
            if (indegree[i] == 0) q.offer(i);
        }

        int[] order = new int[numCourses];
        int idx = 0;
        while (!q.isEmpty()) {
            int cur = q.poll();
            order[idx++] = cur;
            for (int nxt : graph.get(cur)) {
                if (--indegree[nxt] == 0) q.offer(nxt);
            }
        }

        return idx == numCourses ? order : new int[0];
    }
}
func findOrder(numCourses int, prerequisites [][]int) []int {
    graph := make([][]int, numCourses)
    indegree := make([]int, numCourses)

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

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

    order := make([]int, 0, numCourses)
    for head := 0; head < len(q); head++ {
        cur := q[head]
        order = append(order, cur)
        for _, nxt := range graph[cur] {
            indegree[nxt]--
            if indegree[nxt] == 0 {
                q = append(q, nxt)
            }
        }
    }

    if len(order) != numCourses {
        return []int{}
    }
    return order
}
class Solution {
public:
    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> graph(numCourses);
        vector<int> indegree(numCourses, 0);

        for (auto& p : prerequisites) {
            int a = p[0], b = p[1];
            graph[b].push_back(a);
            indegree[a]++;
        }

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

        vector<int> order;
        order.reserve(numCourses);

        while (!q.empty()) {
            int cur = q.front();
            q.pop();
            order.push_back(cur);

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

        if ((int)order.size() != numCourses) return {};
        return order;
    }
};
from collections import deque

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

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

        q = deque(i for i in range(numCourses) if indegree[i] == 0)
        order = []

        while q:
            cur = q.popleft()
            order.append(cur)
            for nxt in graph[cur]:
                indegree[nxt] -= 1
                if indegree[nxt] == 0:
                    q.append(nxt)

        return order if len(order) == numCourses else []
var findOrder = function(numCourses, prerequisites) {
  const graph = Array.from({ length: numCourses }, () => []);
  const indegree = new Array(numCourses).fill(0);

  for (const [a, b] of prerequisites) {
    graph[b].push(a);
    indegree[a]++;
  }

  const queue = [];
  for (let i = 0; i < numCourses; i++) {
    if (indegree[i] === 0) queue.push(i);
  }

  const order = [];
  for (let head = 0; head < queue.length; head++) {
    const cur = queue[head];
    order.push(cur);
    for (const nxt of graph[cur]) {
      indegree[nxt]--;
      if (indegree[nxt] === 0) queue.push(nxt);
    }
  }

  return order.length === numCourses ? order : [];
};

Comments