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
- 1Name the nodes and edges, and build an adjacency list if one is not given.
- 2Decide the traversal: BFS for fewest steps, DFS for exploring or components.
- 3Mark nodes visited when they are first pushed.
- 4Start from every unvisited node if the graph may be disconnected.
- 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 cycleVariants
- 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
| Operation | Cost |
|---|---|
| BFS or DFS | O(V + E) time, O(V) space |
| Grid traversal | O(rows × cols) |
| Topological sort | O(V + E) |
| Union-find, m operations | O(m · α(n)), effectively linear |
50 Graphs problems
Easiest first. Problems marked Free can be solved without Pro.
- 1Archipelago CountFreeEasy
- 2Paint Bucket FillFreeEasy
- 3Largest LandmassFreeEasy
- 4Coastline LengthEasy
- 5Courier Route CheckEasy
- 6Isolated Network GroupsEasy
- 7Degrees of SeparationEasy
- 8Same Cluster QueriesEasy
- 9Adjacency Lists to Edge ListEasy
- 10Tasks Ready on Day OneEasy
- 11Check a Proposed ScheduleEasy
- 12Loop in the Pipeline NetworkEasy
- 13Is the Network a TreeEasy
- 14Reverse Every ArrowEasy
- 15Cluster Size of Each NodeEasy
- 16Spreading MoldMedium
- 17Distance to the Nearest ChargerMedium
- 18Sealed TerritoriesMedium
- 19Hedge Maze ExitMedium
- 20Enclosed LagoonsMedium
- 21Two Drainage BasinsMedium
- 22Distinct Island ShapesMedium
- 23Narrowest BridgeMedium
- 24Vault Key ChainMedium
- 25Exactly K Hops AwayMedium
- 26Quarantine ReachMedium
- 27Point Every Road HomeMedium
- 28Cut-Off PairsMedium
- 29Can Every Module Be FinishedMedium
- 30Smallest Study OrderMedium
- 31Split Into Two SquadsMedium
- 32The Extra CableMedium
- 33Clone and Relabel a NetworkMedium
- 34Merge Customer RecordsMedium
- 35Fewest Work RoundsMedium
- 36States That Always HaltMedium
- 37Rewire to Connect EveryoneMedium
- 38Same or Different RulesMedium
- 39Assign Two ShiftsMedium
- 40Moment Everyone Is LinkedMedium
- 41Demolition ShortcutHard
- 42Word Morph ChainHard
- 43Fewest Bus RidesHard
- 44Visit Every StationHard
- 45Six-Tile SliderHard
- 46Decode the Glyph OrderHard
- 47Repair the Reporting LineHard
- 48Critical Path Finish TimeHard
- 49Two Fleets, One Road NetworkHard
- 50First Edge That Breaks Two-ColouringHard
Solve them in the browser, in Python or JavaScript.