Skip to content

189. Course Schedule

๐Ÿ”— LeetCode Problem: 207. Course Schedule
๐Ÿ“Š Difficulty: Medium
๐Ÿท๏ธ Topics: Graph, DFS, BFS, Topological Sort, Cycle Detection

Problem Statement

There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [aแตข, bแตข] indicates that you must take course bแตข first if you want to take course aแตข.

  • For example, the pair [0, 1] indicates that to take course 0 you have to first take course 1.

Return true if you can finish all courses. Otherwise, return false.

Example 1:

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

Explanation: 
  Total 2 courses: 0 and 1
  To take course 1, you need to take course 0 first
  Schedule: 0 โ†’ 1
  Possible! โœ…

Example 2:

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

Explanation:
  To take course 1, need course 0
  To take course 0, need course 1
  Circular dependency! โŒ
  Impossible!

Constraints: - 1 <= numCourses <= 2000 - 0 <= prerequisites.length <= 5000 - prerequisites[i].length == 2 - 0 <= aแตข, bแตข < numCourses - All the pairs prerequisites[i] are unique.


๐ŸŒณ Visual Understanding - The Complete Picture

The Problem We're Solving ๐Ÿค”

Courses with prerequisites form a dependency graph: ๐Ÿ“šโžก๏ธ๐Ÿ“š

Example:
  numCourses = 4
  prerequisites = [[1,0], [2,0], [3,1], [3,2]]

Meaning:
  [1,0]: To take 1, must take 0 first (0 โ†’ 1)
  [2,0]: To take 2, must take 0 first (0 โ†’ 2)
  [3,1]: To take 3, must take 1 first (1 โ†’ 3)
  [3,2]: To take 3, must take 2 first (2 โ†’ 3)

Graph visualization:
       0
      / \
     โ†“   โ†“
     1   2
      \ /
       โ†“
       3

Question: Can we complete all courses? ๐Ÿค”

Answer: YES! โœ…
Schedule: 0 โ†’ 1 โ†’ 2 โ†’ 3
(or 0 โ†’ 2 โ†’ 1 โ†’ 3)

Building Understanding Through Examples ๐Ÿ“š

Example 1: Simple chain (possible)

prerequisites = [[1,0]]

Graph:
  0 โ†’ 1

Schedule:
  Step 1: Take course 0 โœ…
  Step 2: Take course 1 โœ…

All courses completed! ๐ŸŽ“
Answer: true
Example 2: Circular dependency (impossible)

prerequisites = [[1,0], [0,1]]

Graph:
  0 โ‡„ 1

Dependency cycle:
  Course 0 needs 1
  Course 1 needs 0
  โŸณ Circular! โŒ

Can't start anywhere!
Answer: false
Example 3: Complex DAG (possible)

prerequisites = [[1,0], [2,1], [3,2], [1,3]]

Graph:
       0
       โ†“
       1 โ†โ”€โ”
       โ†“   โ”‚
       2   โ”‚
       โ†“   โ”‚
       3 โ”€โ”€โ”˜

Wait... 1 โ†’ 3 โ†’ 1? That's a cycle! โŒ

Actually:
  [1,0]: 0 โ†’ 1
  [2,1]: 1 โ†’ 2
  [3,2]: 2 โ†’ 3
  [1,3]: 3 โ†’ 1 (creates cycle!)

Cycle detected: 1 โ†’ 2 โ†’ 3 โ†’ 1 โŸณ
Answer: false
Example 4: No prerequisites (always possible)

numCourses = 3
prerequisites = []

No dependencies at all!
Take in any order: 0, 1, 2 or 2, 1, 0, etc.
Answer: true โœ…

The Pattern Emerges ๐Ÿ’ก

Key observations: ๐Ÿ”

1๏ธโƒฃ Directed Graph Problem ๐ŸŽฏ
   Courses = nodes
   Prerequisites = directed edges
   [a,b] means b โ†’ a (b must come before a)

2๏ธโƒฃ Cycle Detection! ๐Ÿ”„
   Cycle = impossible schedule
   DAG (no cycle) = possible schedule

3๏ธโƒฃ Topological Sort Connection ๐Ÿ“Š
   Valid schedule = topological ordering
   Exists only if graph is DAG!

4๏ธโƒฃ Two Approaches ๐Ÿ› ๏ธ
   DFS: Detect cycles directly
   BFS (Kahn's): Topological sort attempt

5๏ธโƒฃ Return Boolean ๐ŸŽฒ
   Just need true/false
   Don't need actual schedule!

๐Ÿง  The Journey From Brute Force to Optimal

Part 1: The Obvious Try - "Generate all orderings?" ๐Ÿค”

๐Ÿ’ญ First thought: "Try all possible course orderings!"

Brute force:
  Generate all permutations of courses
  For each permutation:
    Check if prerequisites satisfied
    If yes: return true

  If no permutation works: return false

Example:
  numCourses = 3
  prerequisites = [[1,0], [2,1]]

  Permutations to try:
    [0,1,2]: 0โ†’1 โœ…, 1โ†’2 โœ… โ†’ Valid! โœ…
    [0,2,1]: 0โ†’1 โŒ (1 comes after 2)
    [1,0,2]: 1 needs 0, but 1 comes first โŒ
    [1,2,0]: Same problem โŒ
    [2,0,1]: 2 needs 1, but 1 comes last โŒ
    [2,1,0]: 2 needs 1 โœ…, 1 needs 0 โŒ

  Found valid: [0,1,2] โ†’ return true

Problem: EXPONENTIAL! ๐Ÿ˜ฑ

Time complexity:
  n! permutations
  Each takes O(E) to verify (E = prerequisites)
  Total: O(n! ร— E) ๐ŸŒ

For 10 courses: 3,628,800 permutations! โŒ
For 20 courses: 2.4 ร— 10^18 permutations! ๐Ÿ’€

Completely infeasible! ๐Ÿšซ

Part 2: The Realization - "It's cycle detection!" ๐Ÿ’ญ

๐Ÿค” Key insight: Cycles make completion impossible!

Why cycles are the problem:
  A โ†’ B โ†’ C โ†’ A

  To take A, need C
  To take C, need B  
  To take B, need A
  โŸณ Infinite loop! Can't start!

Reframe the problem:
  "Can finish all courses?"
  โŸบ
  "Is the dependency graph acyclic?" (DAG)

  If DAG: โœ… Can finish (topological order exists)
  If has cycle: โŒ Cannot finish

So we need: CYCLE DETECTION! ๐Ÿ”„

This is a GRAPH problem!
  Nodes: Courses
  Edges: Prerequisites
  Question: Is graph a DAG?

Two classic approaches:
  1. DFS with cycle detection
  2. BFS (Kahn's algorithm) - topological sort

Both: O(V + E) time! โšก
Much better than O(n!)! ๐Ÿš€

Part 3: The DFS Approach - "Three states for cycle detection!" ๐Ÿ’ก

๐Ÿค” DFS cycle detection using THREE states:

State meanings:
  UNVISITED (0): Haven't explored yet โšช
  VISITING (1): Currently exploring (on path) ๐ŸŸก
  VISITED (2): Completely explored โœ…

Why three states?
  Two states (visited/unvisited) insufficient!
  Need to track "currently on recursion path"

  VISITING state = "on current DFS path"
  If we reach VISITING node again = CYCLE! ๐Ÿ”„

Example trace:
  Graph: 0 โ†’ 1 โ†’ 2 โ†’ 3

  DFS from 0:
    Mark 0 VISITING ๐ŸŸก
    Explore neighbor 1:
      Mark 1 VISITING ๐ŸŸก
      Explore neighbor 2:
        Mark 2 VISITING ๐ŸŸก
        Explore neighbor 3:
          Mark 3 VISITING ๐ŸŸก
          No neighbors
          Mark 3 VISITED โœ…
        Back to 2
        Mark 2 VISITED โœ…
      Back to 1
      Mark 1 VISITED โœ…
    Back to 0
    Mark 0 VISITED โœ…

  No cycles! Return true! โœ…

Example with cycle:
  Graph: 0 โ†’ 1 โ†’ 2 โ†’ 1

  DFS from 0:
    Mark 0 VISITING ๐ŸŸก
    Explore neighbor 1:
      Mark 1 VISITING ๐ŸŸก
      Explore neighbor 2:
        Mark 2 VISITING ๐ŸŸก
        Explore neighbor 1:
          1 is VISITING! ๐Ÿ”ด
          CYCLE DETECTED! โŸณ
          Return false โŒ

Three states are KEY! ๐Ÿ”‘

Part 4: The BFS Approach - "Kahn's algorithm!" ๐Ÿ’ก

๐Ÿค” Alternative: BFS-based topological sort (Kahn's algorithm)

Key idea: Process nodes with no dependencies first!

Algorithm:
  1. Calculate in-degree for each node
     (in-degree = number of prerequisites)

  2. Start with nodes that have in-degree 0
     (no prerequisites - can take immediately)

  3. Process these nodes:
     - "Take" the course
     - Decrease in-degree of dependent courses
     - If dependent course now has in-degree 0, add to queue

  4. If processed all courses: DAG! โœ…
     If some remain: cycle exists! โŒ

Example:
  Graph: 0 โ†’ 1 โ†’ 2

  In-degrees: [0: 0, 1: 1, 2: 1]

  Step 1: Queue = [0] (in-degree 0)
          Process 0, decrease in-degree of 1
          In-degrees: [0: -, 1: 0, 2: 1]
          Queue = [1]

  Step 2: Process 1, decrease in-degree of 2
          In-degrees: [0: -, 1: -, 2: 0]
          Queue = [2]

  Step 3: Process 2
          Queue = []

  Processed 3 courses = all! โœ…
  Return true!

Example with cycle:
  Graph: 0 โ†’ 1 โ†’ 0 (cycle)

  In-degrees: [0: 1, 1: 1]

  Queue = [] (no in-degree 0!)
  Can't process anything!
  Processed 0 courses โ‰  total
  Return false! โŒ

Both approaches work! Choose based on preference! ๐ŸŽฏ

Part 5: The Pattern - "Topological sort problems!" ๐ŸŽฏ

โœจ This is a TOPOLOGICAL SORT problem!

Pattern recognition:
  โœ… Dependencies between items
  โœ… Need to order items respecting dependencies
  โœ… Check if valid ordering exists
  โœ… Detect circular dependencies

Related problems with same pattern:
  - Course Schedule (this one)
  - Course Schedule II (return actual order)
  - Alien Dictionary (deduce alphabet order)
  - Sequence Reconstruction (verify unique order)

Two standard approaches:

1. DFS + Cycle Detection:
   - Three-state tracking
   - If cycle: return false
   - If no cycle: return true
   - Time: O(V + E) โšก

2. BFS (Kahn's Algorithm):
   - In-degree tracking
   - Process zero in-degree nodes
   - Count processed nodes
   - Time: O(V + E) โšก

Both have same complexity!
DFS: More intuitive for cycle detection
BFS: Naturally gives topological order

For this problem (just true/false):
  Either works perfectly! ๐ŸŽฏ

Time: O(V + E) where V = courses, E = prerequisites
Space: O(V + E) for graph representation

Pattern mastered! ๐ŸŒŸ

๐ŸŽฏ Approach 1: DFS with Cycle Detection

The Key Insight ๐Ÿ’ก

Use DFS with three-state tracking to detect cycles ๐Ÿ”„. States: UNVISITED (0), VISITING (1), VISITED (2) ๐ŸŸกโœ…. Start DFS from each unvisited course ๐Ÿš€. If reach VISITING node = cycle found! โŒ If all DFS complete without cycle = DAG! โœ… Return true if no cycles, false otherwise ๐ŸŽฏ. Time: O(V+E) โšก, Space: O(V+E) ๐Ÿ“ฆ!

Direct cycle detection! โœจ

Complete Implementation

class Solution {
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // THREE STATES FOR CYCLE DETECTION ๐Ÿ”„
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    private static final int UNVISITED = 0;  // Not explored โšช
    private static final int VISITING = 1;   // On current path ๐ŸŸก
    private static final int VISITED = 2;    // Fully explored โœ…

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // DFS CYCLE DETECTION APPROACH ๐Ÿ”
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // Build graph and detect cycles using DFS
        // If cycle found: return false โŒ
        // If no cycle: return true โœ…
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // BUILD ADJACENCY LIST ๐Ÿ—๏ธ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            graph.add(new ArrayList<>());
        }

        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        // Add edges: [a,b] means b โ†’ a
        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        for (int[] prereq : prerequisites) {
            int course = prereq[0];
            int prerequisite = prereq[1];
            graph.get(prerequisite).add(course);  // prereq โ†’ course
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // STATE TRACKING ๐Ÿ“Š
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        int[] state = new int[numCourses];  // All start UNVISITED (0)

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // DFS FROM EACH UNVISITED COURSE ๐Ÿš€
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        for (int course = 0; course < numCourses; course++) {
            if (state[course] == UNVISITED) {
                if (hasCycle(graph, state, course)) {
                    return false;  // Cycle detected! โŒ
                }
            }
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // NO CYCLES FOUND! โœ…
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        return true;  // Can finish all courses! ๐ŸŽ“
    }

    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // DFS HELPER: DETECT CYCLE ๐Ÿ”
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    private boolean hasCycle(List<List<Integer>> graph, int[] state, int course) {
        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        // MARK AS VISITING (on current path) ๐ŸŸก
        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        state[course] = VISITING;

        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        // EXPLORE ALL NEIGHBORS ๐Ÿงญ
        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        for (int neighbor : graph.get(course)) {
            if (state[neighbor] == VISITING) {
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                // BACK EDGE! Cycle detected! ๐Ÿ”ด
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                return true;  // Found cycle! โŸณ
            }

            if (state[neighbor] == UNVISITED) {
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                // Continue DFS ๐Ÿ”
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                if (hasCycle(graph, state, neighbor)) {
                    return true;  // Cycle in subtree!
                }
            }

            // If VISITED: already fully explored, skip โœ…
        }

        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        // MARK AS VISITED (fully explored) โœ…
        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        state[course] = VISITED;

        return false;  // No cycle from this course!
    }
}

Why This Works ๐ŸŽฏ

Three states enable cycle detection: ๐Ÿ”„ - UNVISITED: Haven't explored yet - VISITING: Currently on DFS path (KEY!) - VISITED: Completely explored

VISITING state detects cycles: ๐Ÿ”‘ - If we reach VISITING node = back edge - Back edge = cycle in directed graph! - Return false immediately!

VISITED prevents re-exploration: โšก - Already processed, no cycle from here - Skip without recursing - Saves time!


๐ŸŽฏ Approach 2: BFS (Kahn's Algorithm)

The Key Insight ๐Ÿ’ก

Use Kahn's algorithm for topological sort attempt ๐ŸŒŠ. Calculate in-degree for each course (count prerequisites) ๐Ÿ“Š. Start with courses having in-degree 0 (no prerequisites) โœ…. Process them: "take" course, decrease neighbors' in-degrees, add newly-zero to queue ๐Ÿ”„. Count processed courses ๐Ÿ“. If count = total, DAG! โœ… Otherwise cycle exists! โŒ Time: O(V+E) โšก, Space: O(V+E) ๐Ÿ“ฆ!

Topological sort approach! โœจ

Complete Implementation

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // KAHN'S ALGORITHM (BFS TOPOLOGICAL SORT) ๐ŸŒŠ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // Try to build topological order
        // If successful (all processed): DAG โœ…
        // If incomplete: cycle exists โŒ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // BUILD ADJACENCY LIST ๐Ÿ—๏ธ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            graph.add(new ArrayList<>());
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // CALCULATE IN-DEGREES ๐Ÿ“Š
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        int[] inDegree = new int[numCourses];

        for (int[] prereq : prerequisites) {
            int course = prereq[0];
            int prerequisite = prereq[1];

            graph.get(prerequisite).add(course);  // prereq โ†’ course
            inDegree[course]++;  // course has one more prerequisite
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // FIND COURSES WITH NO PREREQUISITES ๐Ÿ”
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        Queue<Integer> queue = new LinkedList<>();

        for (int course = 0; course < numCourses; course++) {
            if (inDegree[course] == 0) {
                queue.offer(course);  // Can take immediately! โœ…
            }
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // BFS: PROCESS COURSES ๐ŸŒŠ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        int processedCount = 0;

        while (!queue.isEmpty()) {
            int course = queue.poll();
            processedCount++;  // "Take" this course โœ…

            // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
            // DECREASE IN-DEGREE OF DEPENDENT COURSES ๐Ÿ“‰
            // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
            for (int dependent : graph.get(course)) {
                inDegree[dependent]--;

                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                // IF NOW READY, ADD TO QUEUE ๐ŸŽฏ
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                if (inDegree[dependent] == 0) {
                    queue.offer(dependent);
                }
            }
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // CHECK IF ALL COURSES PROCESSED โœ…
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // If processed all: no cycle (DAG) โœ…
        // If some remain: cycle exists โŒ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        return processedCount == numCourses;
    }
}

Why This Works ๐ŸŽฏ

In-degree tracks prerequisites: ๐Ÿ“Š - in-degree[i] = number of courses needed before i - When in-degree = 0, course is ready!

BFS processes in topological order: ๐ŸŒŠ - Start with zero in-degree (no dependencies) - Process course, "remove" edges - Newly-zero courses become ready - Natural topological ordering!

Count reveals cycles: ๐Ÿ” - If DAG: can process all courses - If cycle: some courses never reach zero in-degree - processedCount < total = cycle! โŒ


๐Ÿ” Detailed Dry Run

Let's trace Approach 1 (DFS) on a complex example:

numCourses = 4
prerequisites = [[1,0], [2,0], [3,1], [3,2]]

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                            SETUP                             โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Build graph from prerequisites:
  [1,0]: 0 โ†’ 1
  [2,0]: 0 โ†’ 2
  [3,1]: 1 โ†’ 3
  [3,2]: 2 โ†’ 3

Adjacency list:
  0: [1, 2]
  1: [3]
  2: [3]
  3: []

Graph visualization:
       0
      / \
     โ†“   โ†“
     1   2
      \ /
       โ†“
       3

State array: [0, 0, 0, 0] (all UNVISITED)

DFS Exploration ๐Ÿ”

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ ๐Ÿ” DFS FROM COURSE 0                           โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘ state[0] = UNVISITED โ†’ Start DFS               โ•‘
โ•‘                                                 โ•‘
โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•‘
โ•‘ hasCycle(graph, state, 0):                     โ•‘
โ•‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•‘
โ•‘                                                 โ•‘
โ•‘ Mark 0 as VISITING ๐ŸŸก                          โ•‘
โ•‘ state = [1, 0, 0, 0]                           โ•‘
โ•‘                                                 โ•‘
โ•‘ Explore neighbors of 0: [1, 2]                 โ•‘
โ•‘                                                 โ•‘
โ•‘   โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—โ•‘
โ•‘   โ•‘ Neighbor 1:                               โ•‘โ•‘
โ•‘   โ•‘ state[1] = UNVISITED โ†’ Recurse           โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ hasCycle(graph, state, 1):                โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Mark 1 as VISITING ๐ŸŸก                    โ•‘โ•‘
โ•‘   โ•‘ state = [1, 1, 0, 0]                     โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Explore neighbors of 1: [3]              โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘   โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ Neighbor 3:                       โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ state[3] = UNVISITED โ†’ Recurse   โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘                                   โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ hasCycle(graph, state, 3):        โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘                                   โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ Mark 3 as VISITING ๐ŸŸก            โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ state = [1, 1, 0, 1]             โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘                                   โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ Neighbors of 3: [] (empty)       โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ No neighbors to explore          โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘                                   โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ Mark 3 as VISITED โœ…             โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ state = [1, 1, 0, 2]             โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘                                   โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•‘ Return false (no cycle)          โ•‘  โ•‘โ•‘
โ•‘   โ•‘   โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•  โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Back to course 1                          โ•‘โ•‘
โ•‘   โ•‘ No cycle found from 3 โœ…                  โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Mark 1 as VISITED โœ…                     โ•‘โ•‘
โ•‘   โ•‘ state = [1, 2, 0, 2]                     โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Return false (no cycle)                   โ•‘โ•‘
โ•‘   โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•‘
โ•‘                                                 โ•‘
โ•‘ Back to course 0                                โ•‘
โ•‘ No cycle from neighbor 1 โœ…                    โ•‘
โ•‘                                                 โ•‘
โ•‘   โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—โ•‘
โ•‘   โ•‘ Neighbor 2:                               โ•‘โ•‘
โ•‘   โ•‘ state[2] = UNVISITED โ†’ Recurse           โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ hasCycle(graph, state, 2):                โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Mark 2 as VISITING ๐ŸŸก                    โ•‘โ•‘
โ•‘   โ•‘ state = [1, 2, 1, 2]                     โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Explore neighbors of 2: [3]              โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘   Neighbor 3:                             โ•‘โ•‘
โ•‘   โ•‘   state[3] = VISITED โœ…                  โ•‘โ•‘
โ•‘   โ•‘   Already fully explored, skip!          โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Mark 2 as VISITED โœ…                     โ•‘โ•‘
โ•‘   โ•‘ state = [1, 2, 2, 2]                     โ•‘โ•‘
โ•‘   โ•‘                                           โ•‘โ•‘
โ•‘   โ•‘ Return false (no cycle)                   โ•‘โ•‘
โ•‘   โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•‘
โ•‘                                                 โ•‘
โ•‘ Back to course 0                                โ•‘
โ•‘ No cycle from neighbor 2 โœ…                    โ•‘
โ•‘                                                 โ•‘
โ•‘ All neighbors explored, no cycles!              โ•‘
โ•‘                                                 โ•‘
โ•‘ Mark 0 as VISITED โœ…                           โ•‘
โ•‘ state = [2, 2, 2, 2]                           โ•‘
โ•‘                                                 โ•‘
โ•‘ Return false (no cycle from course 0)           โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ ๐Ÿ” CHECK REMAINING COURSES                     โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘ Course 1: state[1] = VISITED โœ… Skip           โ•‘
โ•‘ Course 2: state[2] = VISITED โœ… Skip           โ•‘
โ•‘ Course 3: state[3] = VISITED โœ… Skip           โ•‘
โ•‘                                                 โ•‘
โ•‘ All courses checked!                            โ•‘
โ•‘ No cycles found anywhere! โœ…                    โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

FINAL RESULT ๐ŸŽ‰

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘         ๐ŸŽ‰ FINAL RESULT: true                 โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘                                               โ•‘
โ•‘  Graph is a DAG (no cycles)! โœ…               โ•‘
โ•‘  Can finish all courses! ๐ŸŽ“                   โ•‘
โ•‘                                               โ•‘
โ•‘  Valid schedule exists:                       โ•‘
โ•‘    0 โ†’ 1 โ†’ 2 โ†’ 3                             โ•‘
โ•‘    or 0 โ†’ 2 โ†’ 1 โ†’ 3                          โ•‘
โ•‘                                               โ•‘
โ•‘  State progression:                           โ•‘
โ•‘    Start: [0, 0, 0, 0] (all UNVISITED)       โ•‘
โ•‘    End:   [2, 2, 2, 2] (all VISITED)         โ•‘
โ•‘                                               โ•‘
โ•‘  No VISITING state encountered twice! ๐Ÿ”‘      โ•‘
โ•‘  No cycles detected! โŸณโŒ                      โ•‘
โ•‘                                               โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

๐Ÿ”‘ Key Observations:

  1. Three-state tracking worked perfectly: ๐ŸŸกโœ… VISITING โ†’ VISITED progression

  2. DFS explored all paths: ๐Ÿ” Complete graph traversal

  3. VISITED optimization: โšก Course 3 skipped second time (already explored)

  4. No back edges detected: ๐ŸŽฏ Never reached VISITING node from VISITING path

  5. Result: DAG confirmed: โœ… Can complete all courses!


๐Ÿ“Š Complexity Analysis

Time Complexity: O(V + E) โšก

Where:
  V = numCourses (vertices)
  E = prerequisites.length (edges)

Both approaches (DFS and BFS):

Build graph: O(E)
  - Process each prerequisite once
  - Add to adjacency list

Approach 1 (DFS):
  - Visit each course once: O(V)
  - Explore each edge once: O(E)
  - Total: O(V + E) โœ…

Approach 2 (BFS):
  - Calculate in-degrees: O(E)
  - BFS processes each course once: O(V)
  - Process each edge once: O(E)
  - Total: O(V + E) โœ…

Overall: O(V + E) for both! โšก

Example: 2000 courses, 5000 prerequisites
  Time: 2000 + 5000 = 7000 operations
  Very efficient! ๐Ÿš€

Space Complexity: O(V + E) ๐Ÿ“ฆ

Space usage:

1. Graph (adjacency list): O(V + E) ๐Ÿ—๏ธ
   - V lists (one per course)
   - Total E entries across all lists

2. Approach 1 (DFS):
   - State array: O(V)
   - Recursion stack: O(V) worst case
   - Total: O(V + E) โœ…

3. Approach 2 (BFS):
   - In-degree array: O(V)
   - Queue: O(V) worst case
   - Total: O(V + E) โœ…

Both approaches: O(V + E) ๐Ÿ“ฆ

Example: 2000 courses, 5000 prerequisites
  Graph: ~7000 space
  Additional: 2000 space
  Total: ~9000 space units
  Reasonable! โœ…

Core Pattern: Topological Sort & Cycle Detection ๐Ÿ”„

// Template for topological sort problems
class Solution {
    // DFS approach with cycle detection
    public boolean topologicalSortDFS(int n, int[][] edges) {
        List<List<Integer>> graph = buildGraph(n, edges);
        int[] state = new int[n];  // Three states

        for (int i = 0; i < n; i++) {
            if (state[i] == UNVISITED) {
                if (hasCycle(graph, state, i)) {
                    return false;  // Cycle found
                }
            }
        }
        return true;  // DAG
    }

    // BFS approach (Kahn's algorithm)
    public boolean topologicalSortBFS(int n, int[][] edges) {
        List<List<Integer>> graph = buildGraph(n, edges);
        int[] inDegree = new int[n];

        // Calculate in-degrees
        for (int[] edge : edges) {
            inDegree[edge[0]]++;
        }

        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            if (inDegree[i] == 0) queue.offer(i);
        }

        int count = 0;
        while (!queue.isEmpty()) {
            int node = queue.poll();
            count++;

            for (int neighbor : graph.get(node)) {
                if (--inDegree[neighbor] == 0) {
                    queue.offer(neighbor);
                }
            }
        }

        return count == n;
    }
}

When to Use This Pattern ๐ŸŽฏ

โœ… Dependencies between items
โœ… Check if ordering is possible
โœ… Detect circular dependencies
โœ… Topological ordering needed
โœ… Prerequisite relationships

Recognition triggers: ๐Ÿ” - "Prerequisites" or "dependencies" ๐Ÿ“š - "Order" or "sequence" ๐Ÿ“Š - "Can finish all" or "is possible" โœ… - "Circular dependency" detection ๐Ÿ”„ - Directed graph with constraints ๐ŸŽฏ

Key insight: Topological sort exists โŸบ DAG (no cycles)! ๐Ÿ’ก

Direct Applications:

  1. Course Schedule II (LC 210) - Medium ๐Ÿ“š โ€ข What's similar: Same prerequisites, cycle detection ๐Ÿ”„ โ€ข What's different: Return actual order, not just boolean ๐Ÿ“Š โ€ข Key insight: Add ordering during DFS/BFS! โœจ

  2. Alien Dictionary (LC 269) - Hard ๐Ÿ‘ฝ โ€ข What's similar: Build graph, topological sort ๐ŸŒŠ โ€ข What's different: Deduce graph from word order ๐Ÿ” โ€ข Key insight: Compare adjacent words for edges! ๐Ÿ’ก

  3. Sequence Reconstruction (LC 444) - Medium ๐Ÿ”ข โ€ข What's similar: Topological sort, unique ordering ๐ŸŽฏ โ€ข What's different: Verify if sequences give unique order โœ… โ€ข Key insight: Check uniqueness during Kahn's! ๐Ÿ”‘

  4. Minimum Height Trees (LC 310) - Medium ๐ŸŒณ โ€ข What's similar: Graph, remove leaves (like in-degree 0) ๐ŸŒŠ โ€ข What's different: Undirected graph, find centers ๐ŸŽฏ โ€ข Key insight: Topological sort variation! โšก

Pattern Evolution: ๐Ÿ“ˆ

Level 1: Basic Cycle Detection ๐Ÿ”„
  โ”œโ”€ Course Schedule (LC 207) โ† HERE ๐ŸŽฏ
  โ””โ”€ Detect cycle in directed graph

Level 2: Topological Sort Output ๐Ÿ“Š
  โ”œโ”€ Course Schedule II (LC 210)
  โ”œโ”€ Alien Dictionary (LC 269)
  โ””โ”€ Sequence Reconstruction (LC 444)

Level 3: Advanced Variations ๐ŸŒŸ
  โ”œโ”€ Parallel Courses (LC 1136, 1494)
  โ”œโ”€ Sort Items by Groups (LC 1203)
  โ””โ”€ Find All Recipes (LC 2115)

๐Ÿ“ Quick Revision Notes

๐ŸŽฏ Core Concept

Course Schedule detects if courses can be completed using cycle detection ๐Ÿ”„. Prerequisites form directed graph (course dependencies) ๐Ÿ“šโžก๏ธ๐Ÿ“š. Cycle = impossible (circular dependency) โŒ. DAG = possible (valid schedule exists) โœ…. Two approaches: DFS with three states (UNVISITED, VISITING, VISITED) or BFS (Kahn's) with in-degree tracking ๐ŸŒŠ. Both O(V+E) time โšก. Return true if DAG, false if cycle! This is topological sort pattern! ๐ŸŽฏ

โšก Quick Implementation (DFS)

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

public class Solution {
  public boolean canFinish(int numCourses, int[][] prerequisites) {
    // // Approach 1 - BFS (kahn)
    // return bfs(numCourses, prerequisites);

    // Approach 2 - DFS (plus stack)
    return dfs(numCourses, prerequisites);
  }

  private boolean dfs(int numCourses, int[][] prerequisites) {
    // Step 1: create graph (list of list) - no indegrees
    ArrayList<ArrayList<Integer>> graph = new ArrayList<>();

    for (int i = 0; i < numCourses; i++) {
      graph.add(new ArrayList<>());
    }

    for (int i = 0; i < prerequisites.length; i++) {
      graph.get(prerequisites[i][0]).add(prerequisites[i][1]);
    }

    // State 2: Create and track status of nodes (0, 1, 2) with 0 being default.
    // 0 - UNVISITED, 1 - VISITING and 2 - VISITED
    int[] state = new int[numCourses];

    // State 3: Loop every course and detch to find cycle.
    // If loop found, return false. Else, return true.
    for (int i = 0; i < numCourses; i++) {
      if (state[i] == 0) {
        if (dfsUtil(i, state, graph)) { // has cycle?
          return false;
        }
      }
    }

    return true;
  }

  private boolean dfsUtil(int course, int[] state, ArrayList<ArrayList<Integer>> graph) {
    // Step 4: change the state of course being visited or considered.
    state[course] = 1;

    for (int depCourse : graph.get(course)) {
      // Step 5: check the state of dependent course.
      // If cycle is there, it gets detected here.
      if (state[depCourse] == 1) {
        return true;
      }

      // Prevents infinite loop (acts as visited)
      if (state[depCourse] == 0) {
        // Else, do a DFS from here.
        if (dfsUtil(depCourse, state, graph)) {
          return true;
        }
      }
    }

    // Step 6: Mark the node as visited
    state[course] = 2;

    return false;
  }

  private boolean bfs(int numCourses, int[][] prerequisites) {
    // Step 1: create graph (list of list) and indegrees (array).
    ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
    int[] indegrees = new int[numCourses];

    for (int i = 0; i < numCourses; i++) {
      graph.add(new ArrayList<>());
    }

    for (int i = 0; i < prerequisites.length; i++) {
      graph.get(prerequisites[i][0]).add(prerequisites[i][1]);
      indegrees[prerequisites[i][1]]++;
    }

    // Step 2: find courses with no dependencies (indegrees with 0 value)
    Queue<Integer> queue = new LinkedList<>();
    for (int i = 0; i < numCourses; i++) {
      if (indegrees[i] == 0) {
        queue.offer(i);
      }
    }

    // Step 3: explore queue
    int coursesTaken = 0;
    while (!queue.isEmpty()) {
      int course = queue.poll();
      coursesTaken++; // course taken

      for (int depCourse : graph.get(course)) {
        indegrees[depCourse]--;

        if (indegrees[depCourse] == 0) { // no dependancy anymore
          queue.offer(depCourse);
        }
      }
    }

    return coursesTaken == numCourses;
  }

  public static void main(String[] args) {
    Solution s = new Solution();
    System.out.println(s.canFinish(2, new int[][] { { 1, 0 } })); // true
    System.out.println(s.canFinish(2, new int[][] { { 1, 0 }, { 0, 1 } })); // false
  }
}

๐Ÿ”‘ Key Insights

Three States are Essential: ๐Ÿ”‘

Why three states for DFS cycle detection?

UNVISITED (0): Haven't explored โšช
VISITING (1): Currently on DFS path ๐ŸŸก
VISITED (2): Completely explored โœ…

Example showing why two states fail:
  Graph: 0 โ†’ 1 โ†’ 2, 0 โ†’ 2

With only two states (visited/unvisited):
  DFS from 0:
    Visit 0 (mark visited)
    Go to 1:
      Visit 1 (mark visited)
      Go to 2:
        Visit 2 (mark visited) โœ…
    Back to 0, try other neighbor 2:
      2 is visited! 
      Think it's a cycle? โŒ WRONG!

The problem: Can't distinguish:
  - Back edge (cycle): VISITING โ†’ VISITING
  - Cross edge (no cycle): VISITING โ†’ VISITED

With three states:
  When at 0, going to 2:
    2 is VISITED (not VISITING)
    Not a cycle! โœ…

VISITING = "on current path" ๐Ÿ”‘
Only VISITING โ†’ VISITING is a cycle! ๐Ÿ”„

Edge Direction Matters: โžก๏ธ

CRITICAL: [a, b] means b โ†’ a!

Example: [1, 0]
  Means: "To take 1, need 0 first"
  Edge: 0 โ†’ 1 (not 1 โ†’ 0!)

When building graph:
  prerequisites[i] = [course, prerequisite]
  graph.get(prerequisite).add(course) โœ…

NOT: graph.get(course).add(prerequisite) โŒ

Getting direction wrong = wrong answer! ๐Ÿšซ

DFS vs BFS Trade-offs: โš–๏ธ

DFS Approach (Three States):
  โœ… More intuitive for cycle detection
  โœ… Natural recursion
  โœ… Easier to understand "back edge"
  โŒ Recursion stack space (deep graphs)

BFS Approach (Kahn's Algorithm):
  โœ… Iterative (no stack overflow risk)
  โœ… Naturally gives topological order
  โœ… Clear in-degree logic
  โŒ Slightly more code

Both: O(V + E) time, O(V + E) space

For Course Schedule (just true/false):
  Either works perfectly! ๐ŸŽฏ

For Course Schedule II (need order):
  BFS slightly easier (order is processing order)
  DFS needs post-order collection

Topological Sort Existence: ๐Ÿ“Š

KEY THEOREM:
  Topological sort exists โŸบ Graph is DAG

Why cycles prevent topological sort:
  A โ†’ B โ†’ C โ†’ A (cycle)

  In topological order:
    A must come before B (A โ†’ B)
    B must come before C (B โ†’ C)
    C must come before A (C โ†’ A)

  Contradiction! Can't satisfy all! โŒ

This problem leverages this theorem:
  Can finish courses โŸบ DAG โŸบ No cycles

Just check for cycles! ๐ŸŽฏ

๐ŸŽช Memory Aid

"Three states track the path we roam" ๐ŸŸกโœ…โšช
"VISITING marks the journey home" ๐Ÿ”„
"If VISITING found again, there's a cycle shown" ๐Ÿ”ด
"No cycles? DAG! Courses can be known" ๐ŸŽ“ โœจ

โš ๏ธ Don't Forget

  • Three states in DFS - UNVISITED, VISITING, VISITED! ๐ŸŸกโœ…
  • Edge direction - [a,b] means bโ†’a (prerequisite first)! โžก๏ธ
  • VISITING โ†’ VISITING - that's the cycle! ๐Ÿ”ด
  • Count in BFS - if count < total, cycle exists! ๐Ÿ“Š
  • Both O(V+E) - same complexity for DFS and BFS! โšก
  • Topological sort - exists only for DAGs! ๐ŸŽฏ

You've mastered Course Schedule - topological sort and cycle detection pattern! ๐ŸŒŸ๐Ÿ“šโœจ