LeetCode #210 Medium

Course Schedule II

Same setup as Course Schedule, but return an ordering of all courses that respects every prerequisite, or an empty array if none exists.

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.
graphstopological-sortbfs
Open on LeetCode ↗
02

Intuition

Course schedule ii extends the previous problem from a yes-or-no answer to an actual ordering: return a valid sequence in which all courses can be taken, or an empty array if none exists. The algorithm is unchanged. Kahn's algorithm already produces a valid order as a by-product — the sequence in which courses are dequeued is a topological order, so the only difference is recording it: - Append each course to a result list as it is processed, and return that list if every course was processed. A course is only enqueued once its in-degree reaches 0, meaning every prerequisite has already been placed. So each course necessarily appears after all its prerequisites, which is exactly the required property. The cycle case is handled by the same count comparison. If the result contains fewer courses than the total, some sit in a cycle and no valid ordering exists — return an empty array, not the partial result. Returning what was collected so far is the most common mistake here, and it produces an incomplete order that looks plausible. Multiple valid orderings usually exist, and any is accepted. Which one appears depends on the order courses leave the queue, so a different but equally correct sequence is not a bug. The DFS alternative also works: run a post-order traversal and reverse the finishing order. The reversal is essential, since a node finishes only after all its dependents, giving exactly the wrong direction. Kahn's algorithm avoids that step entirely, which is why it suits this problem better.

How to spot this pattern

Identical to Course Schedule except you keep the order rather than just counting it. The same stall check applies — a short order means a cycle, and the problem asks for an empty array in that case. One algorithm, two return statements.

03

Approach

Try it first

Before reading on: price up what the direct approach costs here, then ask what has to finish before a vertex becomes available. Aim for O(V + E) time and O(V + E) space.

1

Reuse the cycle-detection machinery

This is Course Schedule with output. Kahn's algorithm already produces a valid order as a by-product, so only the recording step is new.

2

Build the graph and in-degrees

The pair [a, b] means b comes before a, so the edge runs from b to a. Count incoming edges per node as before.

3

Record courses as they are dequeued

Append each processed course to a result list. A course is enqueued only once every prerequisite has been placed, so the order is automatically valid.

4

Decrement dependents

Processing a course reduces its dependents' in-degrees, enqueueing any that reach 0. This is what keeps prerequisites ahead of the courses needing them.

5

Return empty on a cycle

If fewer courses were processed than the total, no ordering exists. Return an empty array, not the partial result — returning what was collected is the usual error here.

6

Accept any valid ordering

Multiple correct orders typically exist, and which one appears depends on queue order. A different sequence from the expected output is not necessarily wrong.

7

Cost of the traversal

Each node and edge is handled once, giving O(V + E) time and O(V + E) space for the graph, in-degrees, queue and result.

04

Solution & live demo

▶1from collections import deque
▶2 
▶3class Solution:
▶4 def findOrder(self, numCourses, prerequisites):
▶5 adj = [[] for _ in range(numCourses)]
▶6 indeg = [0] * numCourses
▶7 for a, b in prerequisites:
▶8 adj[b].append(a)
▶9 indeg[a] += 1
▶10 q = deque(i for i in range(numCourses) if indeg[i] == 0)
▶11 order = []
▶12 while q:
▶13 c = q.popleft()
▶14 order.append(c)
▶15 for nxt in adj[c]:
▶16 indeg[nxt] -= 1
▶17 if indeg[nxt] == 0:
▶18 q.append(nxt)
▶19 return order if len(order) == numCourses else []
05

Common pitfalls

Returning a partial order on a cycle

✗ Wrong
return order
✓ Right
return order if len(order) == numCourses else []

A cyclic graph still emits the vertices outside the cycle, producing a plausible-looking but incomplete schedule. The length check is the only thing separating a valid answer from a truncated one.

Reversing the output

✗ Wrong
return order[::-1]
✓ Right
return order

Kahn's algorithm emits vertices in dependency order already — prerequisites leave the queue before the courses that need them. Reversing produces a schedule where every course precedes its own prerequisites.

Using a stack instead of a queue

✗ Wrong
c = stack.pop()
✓ Right
c = q.popleft()

Any valid topological order is accepted, so LIFO also produces a correct answer here — but it changes which order you get, and if the problem or a test harness expects the BFS-canonical ordering the results won't match. Match the structure to the order you intend to produce.

06

Edge cases

A cycle exists

The queue empties early, the result list is shorter than numCourses, and an empty array is returned as specified.

No prerequisites

Every course is takeable from the start, so any permutation is valid; the algorithm returns [0, 1, ..., n-1].

Multiple valid orderings

All are accepted. Using a stack instead of a queue produces a different — still valid — order.

Disconnected components

Handled naturally, since all zero-in-degree vertices are seeded regardless of which component they belong to.

07

Complexity

Time
O(V + E)
Space
O(V + E)
Identical to Course Schedule I, with the pop sequence retained instead of only its length.