Lessons
Arrays
- Two Sum Problem with Solution
- Best Time to Buy and Sell Stock
- Array Contains Duplicates
- Product of Array Except Self: Optimized Approach
- Maximum Subarray Problem
- Maximum Product Subarray
- Find Minimum in Rotated Sorted Array
- Search in Rotated Sorted Array
- Container With Most Water
- Verifying an Alien Dictionary
- Next Permutation
- Remove Duplicates from Sorted Array
- Find First and Last Position of Element in Sorted Array
- Trapping Rain Water
- Median of Two Sorted Arrays
Dynamic Programming
- Climbing Stairs Problem
- Coin Change Problem
- Longest Increasing Subsequence
- Longest Common Subsequence (LCS)
- Word Break Problem
- Combination Sum Problem
- House Robber Problem
- Decode Ways Problem
- Unique Paths Problem
- Pascal's Triangle Problem
- Generate Parentheses Problem
- Jump Game with Dynamic Programming and Greedy Algorithms
- Regular Expression Matching
- Race Car Problem
Graph
Course Schedule Problem
Course Schedule Problem
Imagine you’re a student trying to complete your degree, but you can't take certain classes until you've completed others. This real-world academic planning is the essence of the Course Schedule problem. It's a classic in algorithmic problem-solving and dives deep into course dependencies, prerequisite courses, and class scheduling. Understanding this problem will help you master concepts like topological sort, directed graph traversal, and dependency resolution.
Problem Statement
The goal is simple: Given a list of prerequisite courses, can you finish all your courses without any cyclic dependencies?
You’re given:
- An integer
numCourses
representing the total number of courses. - An array of pairs
prerequisites
where each pair[a, b]
means you must take courseb
beforea
.
Your task: Determine if you can complete all the courses.
This problem translates to checking if a directed graph built from the course prerequisites contains a cycle or not.
Real-World Relevance
Why should you care about the Course Schedule problem?
- It's used in class scheduling systems.
- It forms the basis for creating learning paths in online education platforms.
- It models task dependencies in project management tools.
Building the Directed Graph from Prerequisites
To solve the problem, the first step is to represent the course dependencies as a directed graph.
- Each course is a node.
- An edge from
b → a
meansa
depends onb
.
This graph helps us track which courses must be taken before others.
Detecting Cyclic Dependencies Using Topological Sort
To detect whether it's possible to finish all courses, we must:
- Perform a topological sort of the graph.
- If there's a cycle, a valid course ordering is impossible.
Two common ways to perform topological sort:
- Kahn’s Algorithm (BFS)
- DFS with cycle detection
Let’s explore both.
Solution 1: Topological Sort with Kahn’s Algorithm (BFS)
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
from collections import deque, defaultdict def canFinish(numCourses, prerequisites): graph = defaultdict(list) in_degree = [0] * numCourses # Build the graph and compute in-degrees for course, prereq in prerequisites: graph[prereq].append(course) in_degree[course] += 1 # Initialize the queue with courses having zero in-degree queue = deque([i for i in range(numCourses) if in_degree[i] == 0]) visited_courses = 0 while queue: current = queue.popleft() visited_courses += 1 for neighbor in graph[current]: in_degree[neighbor] -= 1 if in_degree[neighbor] == 0: queue.append(neighbor) return visited_courses == numCourses
Explanation
- We build an adjacency list representing the directed graph.
in_degree
tracks how many prerequisite courses each course has.- We start from courses with no prerequisites and reduce the in-degrees of their neighbors.
- If we can process all nodes, there are no cyclic dependencies.
Solution 2: Topological Sort with DFS and Cycle Detection
python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
def canFinishDFS(numCourses, prerequisites): graph = defaultdict(list) for course, prereq in prerequisites: graph[prereq].append(course) visited = [0] * numCourses # 0 = unvisited, 1 = visiting, 2 = visited def dfs(course): if visited[course] == 1: # cycle detected return False if visited[course] == 2: # already checked return True visited[course] = 1 for neighbor in graph[course]: if not dfs(neighbor): return False visited[course] = 2 return True for i in range(numCourses): if not dfs(i): return False return True
Explanation
- A course marked "visiting" again means a cycle exists.
- This method emphasizes depth-first traversal of the graph data structure.
Choosing the Best Approach
- Kahn's algorithm is intuitive and works great for understanding dependency resolution.
- DFS is elegant and works well when you want a recursive approach to detecting cyclic dependencies.
Time and Space Complexity
Kahn’s Algorithm (BFS)
- Time Complexity: O(N + E), where N = numCourses and E = total prerequisites
- Space Complexity: O(N + E)
DFS Approach
- Time Complexity: O(N + E)
- Space Complexity: O(N + E), including recursion stack
Conclusion
The Course Schedule problem is more than a coding challenge. It’s about modeling real-world course ordering systems, understanding directed graphs, and applying topological sort for dependency resolution. By grasping this problem, you enhance your ability to manage learning paths, automate class scheduling, and design systems free from cyclic dependencies.