#356Plan-DAG Cycle DetectionMedium

Plan-DAG Cycle Detection

Background

A planner that emits sub-tasks with dependencies must produce a directed acyclic graph (DAG) — otherwise the executor loops forever. Cycle detection on the DAG is a precondition for scheduling; without it, a malformed plan slips into the runtime and hangs.

Problem statement

Implement has_cycle(graph) where graph is a dict[node, list[node]] adjacency map. Return True iff the graph contains a directed cycle. Use 3-colour DFS: white (unvisited), gray (in current DFS path), black (fully explored). Hitting a gray node during DFS = back edge = cycle.

Input

  • graphdict[hashable, list[hashable]] adjacency map. Missing keys in the values list are treated as terminal (no outgoing edges).

Output

Returns bool.

Examples

Example 1 — linear chain

Input:  {"a": ["b"], "b": ["c"], "c": []}
Output: False

Example 2 — self-loop

Input:  {"a": ["a"]}
Output: True

Example 3 — three-node cycle

Input:  {"a": ["b"], "b": ["c"], "c": ["a"]}
Output: True

Example 4 — disconnected components, one cyclic

Input:  {"a": ["b"], "b": [], "c": ["d"], "d": ["c"]}
Output: True

Constraints

  • Handle empty graph ({}) → False.
  • Walk every node in case the graph is disconnected.
  • Nodes appearing only as targets (no key in graph) are treated as having no outgoing edges.

Notes

  • Why 3-colour, not 2. A simple visited set distinguishes back edges from cross edges only with extra state — 3-colour DFS makes the distinction inherent.
  • Topological sort gives the same answer. Successful topological sort = acyclic; failure = cyclic. Either approach works; DFS is simpler to reason about.
Python
Loading...

▶ Run executes the 3 visible sample tests below in your browser. Submit runs the full suite — including hidden tests — on the server for an official verdict.

  • Reference example: linear chain is acyclic
  • Example: three-node cycle
  • Sample: self-loop is a cycle