medium Course Schedule
Problem Statement
You have to complete a numCourses number of courses, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must complete course bi first if you want to complete course ai.
Return true if it's possible to finish all the courses given these prerequisites. Otherwise, return false.
Examples
Example 1:
- Input: numCourses = 3, prerequisites =
[[2, 0], [2, 1]] - Expected Output:
true - Justification: You can take course 0 and course 1 first, then take course 2.
Example 2:
- Input: numCourses = 4, prerequisites =
[[1, 0], [2, 1], [3, 2], [1, 3]] - Expected Output:
false - Justification: There is a cycle in the prerequisites: course 1 requires course 3, which requires course 2, which requires course 1.
Example 3:
- Input: numCourses = 5, prerequisites =
[[1, 0], [2, 1], [3, 2], [4, 3]] - Expected Output:
true - Justification: You can take courses in the order 0, 1, 2, 3, and 4 without any conflicts.
Constraints:
- 1 <= numCourses <= 2000
- 0 <= prerequisites.length <= 5000
- prerequisites[i].length == 2
- 0 <= ai, bi < numCourses
- All the pairs prerequisites[i] are unique.
Solution
To solve this problem, model the courses and prerequisites as a directed graph: each course is a node, and each prerequisite [a, b] is a directed edge from b to a. The task becomes: can we produce a topological ordering of all nodes? If a cycle exists, no valid order exists.
We use Kahn's algorithm, a BFS topological sort that repeatedly removes nodes with in-degree zero. Start with every course that has no prerequisites. Each time we "finish" a course, we remove its outgoing edges, which lowers the in-degree of the courses that depended on it. Any course whose in-degree drops to zero can now be taken and is enqueued. If we finish every course, the graph is acyclic and we return true; otherwise a cycle remains and we return false.
Step-by-step Algorithm
- Initialize inDegree and adjList. Create an array
inDegreeof sizenumCoursesinitialized to 0. Create an adjacency listadjListwhere each index holds the list of courses that depend on it. - Fill inDegree and adjList. For each prerequisite
[a, b], incrementinDegree[a]and addatoadjList[b]. - Initialize the queue. Enqueue every course whose
inDegreeis 0. - Process the queue. While the queue is not empty, dequeue a course, increment
completedCourses, and for each dependent course decrement itsinDegree. If a dependent's in-degree becomes 0, enqueue it. - Check completion. If
completedCourses == numCourses, returntrue; otherwise returnfalse.
Algorithm Walkthrough
For numCourses = 5, prerequisites = [[1, 0], [2, 1], [3, 2], [4, 3]]:
- After building the graph:
inDegree = [0, 1, 1, 1, 1]andadjList = {0:[1], 1:[2], 2:[3], 3:[4], 4:[]}. - Only course 0 has in-degree 0, so the queue starts as
[0]andcompletedCourses = 0. - Dequeue 0.
completedCourses = 1. Dependent course 1's in-degree drops to 0; enqueue 1. - Dequeue 1.
completedCourses = 2. Course 2's in-degree drops to 0; enqueue 2. - Dequeue 2.
completedCourses = 3. Course 3's in-degree drops to 0; enqueue 3. - Dequeue 3.
completedCourses = 4. Course 4's in-degree drops to 0; enqueue 4. - Dequeue 4.
completedCourses = 5. The queue is now empty. - Since
completedCourses == numCourses, returntrue.
Code
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Solution {
// Method to check if all courses can be finished
public boolean canFinish(int numCourses, int[][] prerequisites) {
// Create an array to store the number of prerequisites for each course
int[] inDegree = new int[numCourses];
// Create an adjacency list to store which courses depend on a given course
List<List<Integer>> adjList = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
adjList.add(new ArrayList<>());
}
// Fill the inDegree array and adjacency list based on prerequisites
for (int[] pair : prerequisites) {
int course = pair[0];
int prereq = pair[1];
inDegree[course]++;
adjList.get(prereq).add(course);
}
// Use a queue to keep track of courses with no prerequisites
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.add(i);
}
}
// Counter for the number of courses that have been completed
int completedCourses = 0;
// Process courses with no prerequisites
while (!queue.isEmpty()) {
int current = queue.poll();
completedCourses++;
// Reduce the in-degree of all courses that depend on the current course
for (int dependent : adjList.get(current)) {
inDegree[dependent]--;
// If the dependent course has no more prerequisites, add it to the queue
if (inDegree[dependent] == 0) {
queue.add(dependent);
}
}
}
// If all courses have been completed, return true; otherwise, there's a cycle
return completedCourses == numCourses;
}
public static void main(String[] args) {
Solution solution = new Solution();
// Example 1
int numCourses1 = 3;
int[][] prerequisites1 = { { 2, 0 }, { 2, 1 } };
System.out.println(solution.canFinish(numCourses1, prerequisites1)); // true
// Example 2
int numCourses2 = 4;
int[][] prerequisites2 = { { 1, 0 }, { 2, 1 }, { 3, 2 }, { 1, 3 } };
System.out.println(solution.canFinish(numCourses2, prerequisites2)); // false
// Example 3
int numCourses3 = 5;
int[][] prerequisites3 = { { 1, 0 }, { 2, 1 }, { 3, 2 }, { 4, 3 } };
System.out.println(solution.canFinish(numCourses3, prerequisites3)); // true
}
}
Time Complexity
Building the graph: We iterate through the prerequisites array once, taking O(V + E) time, where V = numCourses and E = prerequisites.length.
Processing the graph: Each node and edge is processed at most once during the BFS traversal, taking O(V + E) time.
Therefore, the overall time complexity is O(V + E).
Edge cases the count cleanly handles
Because the answer is simply "did all numCourses nodes get processed?", the tricky inputs need no special code: a self-loop [a, a] gives course a an in-degree it can never shed (it depends on itself), so it is never enqueued and completedCourses < numCourses → false. A course list with no prerequisites at all starts every node at in-degree 0, so all are enqueued immediately → true. A mutual pair [a, b] and [b, a] leaves both with in-degree 1 forever → false.
Space Complexity
Adjacency list: Stores O(V + E) integers.
In-degree array: Stores O(V) integers.
Queue: In the worst case holds O(V) courses.
Thus, the overall space complexity is O(V + E).
Try it yourself
Try solving this question on LeetCode.
Problem and solution structure adapted from DesignGurus. Re-authored and corrected for this guide.
✅ Solution Course Schedule
Problem Statement
You have to complete a numCourses number of courses, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must complete course bi first if you want to complete course ai.
Return true if it's possible to finish all the courses given these prerequisites. Otherwise, return false.
Examples
Example 1:
- Input: numCourses = 3, prerequisites =
[[2, 0], [2, 1]] - Expected Output:
true - Justification: You can take course 0 and course 1 first, then take course 2.
Example 2:
- Input: numCourses = 4, prerequisites =
[[1, 0], [2, 1], [3, 2], [1, 3]] - Expected Output:
false - Justification: There is a cycle in the prerequisites: course 1 requires course 3, which requires course 2, which requires course 1.
Example 3:
- Input: numCourses = 5, prerequisites =
[[1, 0], [2, 1], [3, 2], [4, 3]] - Expected Output:
true - Justification: You can take courses in the order 0, 1, 2, 3, and 4 without any conflicts.
Constraints:
- 1 <= numCourses <= 2000
- 0 <= prerequisites.length <= 5000
- prerequisites[i].length == 2
- 0 <= ai, bi < numCourses
- All the pairs prerequisites[i] are unique.
Solution
To solve this problem, we can model it as a graph where each course is a node, and each prerequisite is a directed edge. We need to check if there are any cycles in this graph. A cycle would mean that it is impossible to complete the courses. We use Kahn's algorithm (BFS topological sort) to detect cycles. The idea is to repeatedly remove nodes whose in-degree is zero: such nodes have no remaining prerequisites and can be taken now. Each time we remove a node, we decrement the in-degree of every course that depends on it. If we manage to remove all numCourses nodes, the graph is acyclic and every course can be finished; if the queue empties while nodes still have a non-zero in-degree, those nodes sit on a cycle and the answer is false.
This approach is effective because it efficiently checks for cycles and uses a graph traversal method that ensures all nodes and edges are examined. By marking the states of nodes, we can quickly detect cycles and avoid redundant work.
Step-by-step Algorithm
-
Initialize inDegree and adjList:
- Create an array
inDegreeof sizenumCoursesinitialized to 0. This array will keep track of the number of prerequisites each course has. - Create an adjacency list
adjListas a dictionary (or hashmap) where each key is a course and its value is a list of courses that depend on it.
- Create an array
-
Fill inDegree and adjList:
- Iterate through the
prerequisitesarray. For each pair[a, b], incrementinDegree[a]by 1 (since courseahas one more prerequisite). Addato the list ofbinadjList(since courseadepends on courseb).
- Iterate through the
-
Initialize the queue:
- Create a queue and add all courses with
inDegreeof 0 (i.e., courses with no prerequisites).
- Create a queue and add all courses with
-
Process the queue:
- Initialize a counter
completedCoursesto 0. - While the queue is not empty:
- Dequeue a course from the front of the queue.
- Increment
completedCoursesby 1. - For each course that depends on the dequeued course (found in
adjList):- Decrement the
inDegreeof that course by 1. - If
inDegreeof that course becomes 0, enqueue it.
- Decrement the
- Initialize a counter
-
Check if all courses are completed:
- If
completedCoursesequalsnumCourses, returntrue(all courses can be completed). - Otherwise, return
false(it's not possible to complete all courses due to a cycle).
- If
Algorithm Walkthrough
Let's go through the algorithm step-by-step for the input:
numCourses = 5prerequisites = [[1, 0], [2, 1], [3, 2], [4, 3]]
-
Initialize inDegree and adjList:
inDegree= [0, 0, 0, 0, 0]adjList= {0: [], 1: [], 2: [], 3: [], 4: []}
-
Fill inDegree and adjList:
- For
[1, 0]:inDegree= [0, 1, 0, 0, 0]adjList= {0: [1], 1: [], 2: [], 3: [], 4: []}
- For
[2, 1]:inDegree= [0, 1, 1, 0, 0]adjList= {0: [1], 1: [2], 2: [], 3: [], 4: []}
- For
[3, 2]:inDegree= [0, 1, 1, 1, 0]adjList= {0: [1], 1: [2], 2: [3], 3: [], 4: []}
- For
[4, 3]:inDegree= [0, 1, 1, 1, 1]adjList= {0: [1], 1: [2], 2: [3], 3: [4], 4: []}
- For
-
Initialize the queue:
queue= [0] (only course 0 has no prerequisites)
-
Process the queue:
- Initialize
completedCourses= 0 - While
queueis not empty:- Dequeue course 0:
queue= []- Increment
completedCoursesto 1 - For course 1 (dependent on 0):
- Decrement
inDegree[1]to 0 - Enqueue course 1
- Decrement
queue= [1]
- Dequeue course 1:
queue= []- Increment
completedCoursesto 2 - For course 2 (dependent on 1):
- Decrement
inDegree[2]to 0 - Enqueue course 2
- Decrement
queue= [2]
- Dequeue course 2:
queue= []- Increment
completedCoursesto 3 - For course 3 (dependent on 2):
- Decrement
inDegree[3]to 0 - Enqueue course 3
- Decrement
queue= [3]
- Dequeue course 3:
queue= []- Increment
completedCoursesto 4 - For course 4 (dependent on 3):
- Decrement
inDegree[4]to 0 - Enqueue course 4
- Decrement
queue= [4]
- Dequeue course 4:
queue= []- Increment
completedCoursesto 5
- Dequeue course 0:
- Initialize
-
Check if all courses are completed:
completedCourses= 5- Since
completedCoursesequalsnumCourses, returntrue
Code
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Solution {
// Method to check if all courses can be finished
public boolean canFinish(int numCourses, int[][] prerequisites) {
// Create an array to store the number of prerequisites for each course
int[] inDegree = new int[numCourses];
// Create an adjacency list to store which courses depend on a given course
List> adjList = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
adjList.add(new ArrayList<>());
}
// Fill the inDegree array and adjacency list based on prerequisites
for (int[] pair : prerequisites) {
int course = pair[0];
int prereq = pair[1];
inDegree[course]++;
adjList.get(prereq).add(course);
}
// Use a queue to keep track of courses with no prerequisites
Queue queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.add(i);
}
}
// Process courses with no prerequisites
int completedCourses = 0;
while (!queue.isEmpty()) {
int course = queue.poll();
completedCourses++;
for (int nextCourse : adjList.get(course)) {
inDegree[nextCourse]--;
if (inDegree[nextCourse] == 0) {
queue.add(nextCourse);
}
}
}
// If all courses are completed, return true
return completedCourses == numCourses;
}
public static void main(String[] args) {
Solution solution = new Solution();
// Example 1
int numCourses1 = 3;
int[][] prerequisites1 = { { 2, 0 }, { 2, 1 } };
System.out.println(solution.canFinish(numCourses1, prerequisites1)); // true
// Example 2
int numCourses2 = 4;
int[][] prerequisites2 = { { 1, 0 }, { 2, 1 }, { 3, 2 }, { 1, 3 } };
System.out.println(solution.canFinish(numCourses2, prerequisites2)); // false
// Example 3
int numCourses3 = 5;
int[][] prerequisites3 = { { 1, 0 }, { 2, 1 }, { 3, 2 }, { 4, 3 } };
System.out.println(solution.canFinish(numCourses3, prerequisites3)); // true
}
}
Complexity Analysis
Time Complexity
- Building the graph: We iterate through the
prerequisitesarray to build the adjacency list and in-degree array. This takestime, where E is the number of edges (or prerequisites). - Processing the graph: We use a queue to process nodes with zero in-degrees. In the worst case, we process all nodes and all edges once. This step takes
time, where V is the number of vertices (courses).
Therefore, the overall time complexity is
Space Complexity
- Adjacency list: The adjacency list representation of the graph requires
space. - In-degree array: The in-degree array requires
space. - Queue: In the worst case, the queue can hold up to
nodes.
Thus, the overall space complexity is
Recognize it: Nodes + edges, reachability / shortest-unweighted / cycles / components → BFS or DFS with a visited set.
▶ Visualize this problem (step it, predict each fork)
🤖 Don't fully get this? Learn it with Claude
Stuck on Course Schedule? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Progressively stronger hints — you still solve it.
I'm working on the problem **Course Schedule** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
See the technique, not just code.
Explain the optimal approach to **Course Schedule** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Catch bugs, edge cases, sub-optimality.
I'll paste my solution to **Course Schedule**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Lock in recognition with look-alikes.
Give me 2 problems that use the SAME underlying pattern as **Course Schedule**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.