Graph traversal · Topological sort (Kahn's algorithm)
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 any order in which you can take all the courses. If it is impossible, return an empty array.
You are choosing university courses. Some courses have a prerequisite: you must finish course b before you start course a. In what order can you take all the courses?
b before a", course b comes earlier in the list.Draw each rule as an arrow from the prerequisite to the course it unlocks: b → a. Now a valid order is simply a list in which every arrow points forward. The first course in the list must be one with no arrow pointing into it.
The direct idea: try every possible order of the courses, and return the first one that obeys every rule.
There are n! orders (n × (n − 1) × … × 1), and checking each one takes a pass over all the rules. With only twelve courses that is already 479 million orders. It is far too slow.
But look at what a valid order is: a list where all arrows point forward. We do not need to search for it. We can build it from the left, one course at a time.
At each step, which course is safe to put next?
A course is safe when it is not waiting for anything: all its prerequisites are already in the list. Count, for every course, how many prerequisites it still waits for. This count is called its in-degree (how many arrows point into it). A course with in-degree 0 is safe, because nothing it needs is missing.
So: take any safe course and put it in the list. Then cross it off. Every course it unlocked now waits for one fewer prerequisite. Any course whose count drops to 0 has just become safe, so it joins the line of safe courses. Repeat until nobody is left.
Why is it safe? A course with in-degree 0 has no prerequisite left outside the list, so placing it next can never break a rule. And if we get stuck with courses left but none safe, every remaining course is waiting on another remaining course. That means there is a loop of prerequisites, and no valid order exists.
By hand, with 6 courses and the rules [1,0] [2,0] [3,1] [3,2] [4,3] [5,3]. The in-degrees are: course 0 has 0, courses 1 and 2 have 1, course 3 has 2, courses 4 and 5 have 1.
[0].[0]. It unlocks 1 and 2: both drop to 0, so both join the queue: [1, 2].[0, 1]. It unlocks 3, which drops from 2 to 1. Not safe yet.[0, 1, 2]. It unlocks 3, which drops to 0. The queue is [3].[0, 1, 2, 3]. It unlocks 4 and 5, both drop to 0: queue [4, 5].[0, 1, 2, 3, 4, 5]. That has all 6 courses, so we return it.With the loop [1,0] [2,1] [3,2] [1,3], only course 0 starts safe. Taking it only lowers course 1 from 2 to 1, so nothing new joins the queue. We stop with 1 course in the answer, not 4, so we return [].
Press Play to watch the queue and the in-degree numbers change.
These are the questions that usually come next. Open one to see an answer you could say out loud.
What they are checkingWhether you see that the queue already holds every course that is safe to take at the same time.
A strong answerI process the queue in rounds. In each round I take out everything that is in the queue right now, and that is one semester. The courses they unlock go into the queue for the next round. The number of rounds is the number of semesters. If there is a loop, I never finish all the courses, and I return -1 (or whatever the problem asks for).
What they are checkingWhether you can read useful information out of the failure case instead of only reporting it.
A strong answerWhen the queue empties early, the courses that never reached in-degree 0 are the ones that are in a loop or wait on one. They are exactly the courses missing from the answer. To report one actual loop, I would start from one of them and keep following a prerequisite that is also in that leftover group until a course repeats. That repeated course is on a loop.
What they are checkingWhether you know why the queue gives "any valid order", and what to use when the order matters.
A strong answerA plain queue returns whichever safe course arrived first, which is a valid order but not a chosen one. To always take the smallest, I swap the queue for a min-heap. The heap hands out the smallest safe course, and unlocked courses are added to it. The rest of the algorithm stays the same. The cost goes from O(V + E) to O(V log V + E), because each heap step takes about log V.