Course Schedule I

72.1k
0

Given numCourses courses labeled from 0 to numCourses - 1 and a list of prerequisite pairs, return whether all courses can be finished.

Each pair [course, prerequisite] means the prerequisite course must be completed before the course. Completion is possible only when the prerequisite graph has no directed cycle.

Example 1

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

Output: true

Explanation: Course 0 can be completed before course 1.

Example 2

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

Output: false

Explanation: Course 0 and course 1 depend on each other, forming a cycle.

Approach 1

Each course becomes a vertex, and every pair creates a directed edge from prerequisite to dependent course. Depth First Search can detect whether a dependency chain returns to a course already active in the current DFS path.

A visited array tracks courses already explored globally, while a path-visited array tracks courses in the active recursion chain. Reaching a path-visited course means circular dependency, so completing every course becomes impossible.

Algorithm

  • Build an adjacency list with a directed edge from each prerequisite course to its dependent course.

  • Initialize visited and pathVisited arrays to track globally explored courses and courses in the current DFS path.

  • Traverse every course to cover all disconnected components.

  • Start DFS from each course that has not been visited.

  • Mark the current course as visited and pathVisited.

  • Run DFS for every unvisited dependent course and return false if any dependent course is already pathVisited.

  • Remove the current course from pathVisited after all its dependencies are processed.

  • Return true if no circular dependency is found.

Dry Run

course schedule

course schedule

Solution

#include <bits/stdc++.h>
using namespace std;
class Solution {
private:
// Detects a cycle through the active DFS path.
bool hasCycle(int course, vector<int>& visited, vector<int>& pathVisited, vector<vector<int>>& adj) {
// Mark the course as visited.
visited[course] = 1;
// Mark the course in the current DFS path.
pathVisited[course] = 1;
// Explore all courses depending on the current course.
for (int nextCourse : adj[course]) {
// Visit the next course if it is not visited.
if (!visited[nextCourse]) {
// If a cycle is found deeper, return true.
if (hasCycle(nextCourse, visited, pathVisited, adj)) {
return true;
}
}
// If next course is in the current path, a cycle exists.
else if (pathVisited[nextCourse]) {
return true;
}
}
// Remove course from the active DFS path.
pathVisited[course] = 0;
// No cycle found from this course.
return false;
}
public:
// Checks if all courses can be finished.
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> adj(numCourses);
// Build edge from prerequisite to dependent course.
for (auto& pairValue : prerequisites) {
int course = pairValue[0];
int prerequisite = pairValue[1];
adj[prerequisite].push_back(course);
}
vector<int> visited(numCourses, 0);
vector<int> pathVisited(numCourses, 0);
// Start DFS from every unvisited course.
for (int course = 0; course < numCourses; course++) {
// Only unvisited courses can start a new DFS.
if (!visited[course]) {
// If a cycle exists, all courses cannot be finished.
if (hasCycle(course, visited, pathVisited, adj)) {
return false;
}
}
}
// No circular dependency exists.
return true;
}
};
// Driver code.
int main() {
int numCourses = 2;
vector<vector<int>> prerequisites = {
{1, 0}
};
Solution sol;
cout << (sol.canFinish(numCourses, prerequisites) ? "true" : "false");
return 0;
}

Complexity Analysis

Time Complexity: O(V+E), where V is the number of courses and E is the number of prerequisite relations; DFS processes every course and directed edge once.

Space Complexity: O(V+E), where the adjacency list stores V courses and E edges, while the visited arrays and recursion stack require O(V) space.

Approach 2

Kahn Algorithm treats courses with indegree 0 as immediately available because no prerequisites block them. Completing such a course removes outgoing dependency effect from dependent courses.

If all courses can be processed through repeated zero-indegree removal, no cycle exists. If some courses never reach indegree 0, a circular dependency remains.

Algorithm

  • Build an adjacency list and an indegree array from all prerequisite pairs.

  • Add every course with indegree = 0 to a queue.

  • Initialize completed as 0.

  • Pop a course from the queue and increase completed.

  • Reduce the indegree of every dependent course.

  • Add a dependent course to the queue when its indegree becomes 0.

  • Continue until the queue becomes empty.

  • Return true if completed == numCourses; otherwise, return false.

Dry Run

Diagram 1
1 / 2

Diagram 1

Solution

#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
// Checks if all courses can be finished using Kahn's Algorithm.
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> adj(numCourses);
vector<int> indegree(numCourses, 0);
// Build graph and indegree from prerequisites.
for (auto& pairValue : prerequisites) {
int course = pairValue[0];
int prerequisite = pairValue[1];
adj[prerequisite].push_back(course);
indegree[course]++;
}
queue<int> q;
// Add courses with no prerequisites.
for (int course = 0; course < numCourses; course++) {
// Zero indegree means this course can be completed now.
if (indegree[course] == 0) {
q.push(course);
}
}
int completed = 0;
// Complete courses in prerequisite-safe order.
while (!q.empty()) {
int course = q.front();
q.pop();
// Count the current course as completed.
completed++;
// Remove current course as prerequisite for dependent courses.
for (int nextCourse : adj[course]) {
indegree[nextCourse]--;
// If all prerequisites are done, add this course.
if (indegree[nextCourse] == 0) {
q.push(nextCourse);
}
}
}
// All courses are possible only when all get completed.
return completed == numCourses;
}
};
// Driver code.
int main() {
int numCourses = 2;
vector<vector<int>> prerequisites = {
{1, 0},
{0, 1}
};
Solution sol;
cout << (sol.canFinish(numCourses, prerequisites) ? "true" : "false");
return 0;
}

Complexity Analysis

Time Complexity: O(V+E), where V is the number of courses and E is the number of prerequisite relations; every course and directed edge is processed once.

Space Complexity: O(V+E), where the adjacency list stores V courses and E edges, while the indegree array and queue require O(V) space.

Interview follow-up Questions

Courses are vertices, and prerequisite pairs are directed edges from prerequisite to dependent course.

Graph

Read Similar Blogs

Comments0