Pattern 11 of 20

Graphs

Model the problem as nodes and edges, then traverse with BFS or DFS.

When to use it

  • Grids of cells, networks, dependencies or relationships
  • Counting connected regions or checking reachability
  • Shortest path when every step costs the same

The idea

Write down what the nodes and edges are, even when the input is a grid or a list of words. Then a visited set and a traversal answer reachability, components and unweighted shortest paths.

Typical cost O(V + E) time and space.

How it works

A graph is anything made of things and connections: cities and roads, courses and prerequisites, grid cells and their neighbors, words one letter apart. The first step is always to see the graph, then pick a traversal.

Depth-first search explores one path to its end before backing up; it is natural for connected components, cycle detection and anything recursive. Breadth-first search expands in rings of equal distance, so the first time it reaches a node is by the fewest edges. Use BFS for shortest paths when every edge costs the same.

Every traversal needs a visited set, marked when a node is discovered rather than when it is processed, so nothing enters the queue twice. In a grid, the grid itself can serve as the visited marker.

Directed dependency problems ask for a topological order: repeatedly take nodes with no remaining prerequisites. If some nodes never reach zero, there is a cycle. Union-find answers 'are these connected?' incrementally as edges arrive.

How to attack it

  1. 1Name the nodes and edges, and build an adjacency list if one is not given.
  2. 2Decide the traversal: BFS for fewest steps, DFS for exploring or components.
  3. 3Mark nodes visited when they are first pushed.
  4. 4Start from every unvisited node if the graph may be disconnected.
  5. 5For dependencies, use in-degrees and a queue (topological sort).

Template

A skeleton to adapt, not a solution to paste. Write it from memory a few times.

from collections import deque


def shortest_steps(graph, start, goal):
    # BFS: the first time we reach a node is by the fewest edges.
    dist = {start: 0}
    queue = deque([start])
    while queue:
        node = queue.popleft()
        if node == goal:
            return dist[node]
        for nxt in graph[node]:
            if nxt not in dist:          # mark on discovery
                dist[nxt] = dist[node] + 1
                queue.append(nxt)
    return -1


def topo_order(n, edges):
    graph = [[] for _ in range(n)]
    indegree = [0] * n
    for a, b in edges:                   # edge a -> b
        graph[a].append(b)
        indegree[b] += 1
    queue = deque(i for i in range(n) if indegree[i] == 0)
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in graph[node]:
            indegree[nxt] -= 1
            if indegree[nxt] == 0:
                queue.append(nxt)
    return order if len(order) == n else []   # [] means a cycle

Variants

Connected components
Run DFS or BFS from each unvisited node and count the launches. Islands in a grid are the same thing.
Multi-source BFS
Push every source into the queue at distance 0; distances then measure the nearest source, as in rotting fruit or distance to the nearest gate.
Topological sort
Kahn's algorithm with in-degrees, or DFS post-order reversed; a leftover node signals a cycle.
Cycle detection
Undirected: a visited neighbor that is not the parent. Directed: a node seen while still on the current DFS path (three colors).
Union-find
Merge sets as edges arrive and ask whether two nodes share a root; near O(1) with path compression and union by size.

Pitfalls

  • Marking nodes visited when popped instead of when pushed, which enqueues duplicates.
  • Starting only from node 0 in a graph that may be disconnected.
  • Using DFS when the question asks for the fewest steps.
  • Building an undirected adjacency list with only one direction of each edge.
  • Deep recursion on a large grid; use an explicit stack or BFS.

What it costs

OperationCost
BFS or DFSO(V + E) time, O(V) space
Grid traversalO(rows × cols)
Topological sortO(V + E)
Union-find, m operationsO(m · α(n)), effectively linear

50 Graphs problems

Easiest first. Problems marked Free can be solved without Pro.

  1. 1Archipelago CountFreeEasy
  2. 2Paint Bucket FillFreeEasy
  3. 3Largest LandmassFreeEasy
  4. 4Coastline LengthEasy
  5. 5Courier Route CheckEasy
  6. 6Isolated Network GroupsEasy
  7. 7Degrees of SeparationEasy
  8. 8Same Cluster QueriesEasy
  9. 9Adjacency Lists to Edge ListEasy
  10. 10Tasks Ready on Day OneEasy
  11. 11Check a Proposed ScheduleEasy
  12. 12Loop in the Pipeline NetworkEasy
  13. 13Is the Network a TreeEasy
  14. 14Reverse Every ArrowEasy
  15. 15Cluster Size of Each NodeEasy
  16. 16Spreading MoldMedium
  17. 17Distance to the Nearest ChargerMedium
  18. 18Sealed TerritoriesMedium
  19. 19Hedge Maze ExitMedium
  20. 20Enclosed LagoonsMedium
  21. 21Two Drainage BasinsMedium
  22. 22Distinct Island ShapesMedium
  23. 23Narrowest BridgeMedium
  24. 24Vault Key ChainMedium
  25. 25Exactly K Hops AwayMedium
  26. 26Quarantine ReachMedium
  27. 27Point Every Road HomeMedium
  28. 28Cut-Off PairsMedium
  29. 29Can Every Module Be FinishedMedium
  30. 30Smallest Study OrderMedium
  31. 31Split Into Two SquadsMedium
  32. 32The Extra CableMedium
  33. 33Clone and Relabel a NetworkMedium
  34. 34Merge Customer RecordsMedium
  35. 35Fewest Work RoundsMedium
  36. 36States That Always HaltMedium
  37. 37Rewire to Connect EveryoneMedium
  38. 38Same or Different RulesMedium
  39. 39Assign Two ShiftsMedium
  40. 40Moment Everyone Is LinkedMedium
  41. 41Demolition ShortcutHard
  42. 42Word Morph ChainHard
  43. 43Fewest Bus RidesHard
  44. 44Visit Every StationHard
  45. 45Six-Tile SliderHard
  46. 46Decode the Glyph OrderHard
  47. 47Repair the Reporting LineHard
  48. 48Critical Path Finish TimeHard
  49. 49Two Fleets, One Road NetworkHard
  50. 50First Edge That Breaks Two-ColouringHard

Solve them in the browser, in Python or JavaScript.