Course Schedule - JS Solution

JS
Editorial

Solution explanation

Solution explanation

Approach

We model courses and prerequisites as a directed graph: each pair [course, prereq] represents an edge prereq β†’ course (you must take prereq before course). The question β€œCan we finish all courses?” becomes: Is this directed graph cycle-free?

🧱 Graph Representation

  • numPrereqs[i]: indegree-style counter β€” how many prerequisites are still unmet for course i.
  • courseToDependents[i]: adjacency list β€” all courses that directly depend on course i. If we β€œtake” course i, each of its dependents has one fewer unmet prerequisite.

πŸŽ’ Initial Ready Stack

  • We scan all courses and collect those with numPrereqs[i] === 0 into readyCourses. These are courses that have no prerequisites and can be taken immediately.
  • readyCourses is used as a stack: new ready courses are pushed to the top, and we pop from the top when choosing the next course.

πŸ” Processing (Indegree-Based Topological Traversal)

  • While readyCourses is not empty:
    • Pop a course from the top (treating ready courses in a LIFO order) and mark it as taken.
    • For each dependent course that listed this course as a prerequisite:
      • Decrement its numPrereqs by 1.
      • If it drops to 0, push that course onto readyCourses, since it is now ready to be taken.
  • We maintain a counter coursesTaken. Every time we pop a course from readyCourses, we increment this count.

πŸ” Return Value

The function returns the result of coursesTaken === numCourses.

  • It returns true when it is possible to finish all numCourses β€” meaning the graph has no cycles and every course can eventually be taken.
  • It returns false when there exists at least one cycle in the prerequisite graph, since in that case, some courses never reach numPrereqs == 0 and therefore remain impossible to take.
  • The variable coursesTaken tracks how many courses have been successfully completed. If this count equals numCourses, all courses are finishable; otherwise, a cycle must exist.

🧠 Why Any Order of Ready Courses Will Yield Correct Result

  • At any step, every course in readyCourses has all its prerequisites already satisfied.
  • No matter which ready course you pick next (whether from the top of a stack, the front of a queue, or any other ready element), you never violate prerequisite rules, because each of them is currently independently eligible to be taken.
  • If a valid topological ordering exists (i.e., there is no cycle), repeatedly taking any available ready course will always allow us to eventually take all numCourses courses.
  • If there is a cycle, at least one course in that cycle will never see its numPrereqs drop to 0, so it never enters readyCourses. We then finish with coursesTaken < numCourses, which correctly signals that not all courses can be completed.
  • To keep the algorithm efficient, we implement readyCourses using a structure with O(1) insertion and removal at one end (a stack with push/pop, as in this implementation, or a queue). Because any order of ready-course selection is valid, we do not pay extra complexity for this choice.

🧭 DFS-Style vs BFS-Style & Relation to Kahn’s Algorithm

  • In this implementation, readyCourses is treated as a stack using push/pop. This gives a depth-first flavor: once a course becomes ready, we tend to follow its downstream dependents as far as possible before switching to other branches.
  • If we instead used readyCourses as a queue (using push/shift or similar), we would process courses in a breadth-first manner β€” handling all currently ready courses layer by layer. This is the classic Kahn’s Algorithm for topological sort.
  • Both the stack-based (DFS-style) and queue-based (BFS-style / Kahn’s) variants:
    • Use the same in-degree idea (numPrereqs) and dependency lists.
    • Have the same asymptotic complexity: O(N + E) time and O(N + E) space.
    • Differ only in which ready node is chosen next, not in correctness.
  • The choice affects the shape of readyCourses during execution:
    • Stack β†’ deeper chains first (more depth-first behavior).
    • Queue β†’ level by level (more breadth-first behavior).
    • In practice, choosing between a stack (DFS) or a queue (BFS) can slightly affect memory usage for readyCourses: a stack may be more efficient when the graph is deep but narrow (small maximum depth), while a queue can be preferable when the graph is shallow but wide (large maximum level width). However, this does not affect the asymptotic space complexity, which remains O(N + E) in both cases.

Time Complexity

Let N be the number of courses and E be the number of prerequisite pairs.

  • Building the graph structures β€” initializing and filling courseToDependents and numPrereqs β€” takes O(N + E) time when using arrays, since we allocate storage for all N courses and process each of the E prerequisite pairs once. If instead a Map were used and keys were added lazily, the initialization part would disappear, reducing this step’s cost to O(E).
  • Each course is added to and removed from readyCourses at most once, contributing O(N).
  • Each edge (prereq β†’ course) is inspected once when the prereq is taken, which adds another O(E).

Therefore, the overall time complexity is O(N + E).

Space Complexity

Let N be the number of courses and E be the number of prerequisite pairs.

  • The courseToDependents structure stores up to E total dependencies across N courses, requiring O(N + E) space when implemented as an array of lists. If implemented as a Map, the cost becomes O(E) since only courses that appear as prerequisites would be the keys.
  • The numPrereqs array tracks one integer per course, using O(N) space.
  • The readyCourses and the variable coursesTaken together require at most O(N) additional space. In practice, the actual size of readyCourses depends on the graph’s structure β€” it is proportional to the maximum depth since we are using a stack (DFS) but would be proportional to maximum level width if we were using a queue (BFS) β€” but it is always bounded by O(N) in the worst case.

Therefore, the overall space complexity is O(N + E).

Visucodize editorial solution titled JS Solution for LeetCode's Course Schedule coding problem. Includes code and may include explanation. You can animate the code step by step using the provided input by clicking the run button, or fork it locally to update the code and input for custom visualization. View the original problem at https://leetcode.com/problems/course-schedule.