Graphs
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
When it applies
A problem is a graph problem whenever things are connected to other things and the question is about reaching, grouping or ordering them: cells of a grid that touch, people who know each other, courses that require other courses. Name the nodes and the edges first; the algorithm follows.
- Breadth-first search visits nodes in order of distance from the start, so the first time it reaches a node is by a shortest path. Use it for "fewest steps", "minimum moves", "how many minutes until".
- Depth-first search explores one branch fully before the next. Use it for "how many groups", "is there a path", "which cells are enclosed".
- Topological sort orders a directed graph so every edge points forwards. It exists only if there is no cycle, which makes it the test for "can all of these be done".
- Dijkstra's algorithm replaces the queue with a heap when edges have weights: the nearest unsettled node is always final. Use it for "shortest time", "cheapest route", "least effort".
- Union-find keeps groups up to date as edges arrive, answering "are these two connected?" in near-constant time. Use it for counting groups, spotting the edge that closes a cycle, and Kruskal's minimum spanning tree.
The one rule that prevents most bugs in breadth-first search: mark a node visited when you put it in the queue, not when you take it out. Dijkstra is the exception that proves the rule: there a node is final only when it leaves the heap, because a cheaper route may still arrive after it was first seen.
The template
Write this from memory. Every problem in this section is a specialisation of it, and most bugs come from deviating without a reason.
from collections import deque
q = deque([start])
seen = {start} # mark on entry, not on exit
dist = 0
while q:
for _ in range(len(q)): # one ring of the search: every node at distance dist
node = q.popleft()
if is_goal(node):
return dist
for nxt in neighbours(node):
if nxt not in seen:
seen.add(nxt)
q.append(nxt)
dist += 1
return -1queue<int> q;
vector<bool> seen(n, false);
q.push(start); seen[start] = true; // mark on entry, not on exit
for (int dist = 0; !q.empty(); ++dist) {
for (int k = q.size(); k > 0; --k) { // one ring: distance dist
int node = q.front(); q.pop();
if (isGoal(node)) return dist;
for (int nxt : adj[node])
if (!seen[nxt]) { seen[nxt] = true; q.push(nxt); }
}
}
return -1;ArrayDeque<Integer> q = new ArrayDeque<>();
boolean[] seen = new boolean[n];
q.add(start); seen[start] = true; // mark on entry, not on exit
for (int dist = 0; !q.isEmpty(); dist++) {
for (int k = q.size(); k > 0; k--) { // one ring: distance dist
int node = q.poll();
if (isGoal(node)) return dist;
for (int nxt : adj.get(node))
if (!seen[nxt]) { seen[nxt] = true; q.add(nxt); }
}
}
return -1;const q = [start];
const seen = new Set([start]); // mark on entry, not on exit
for (let dist = 0, head = 0; head < q.length; dist++) {
for (let end = q.length; head < end; head++) { // one ring: distance dist
const node = q[head];
if (isGoal(node)) return dist;
for (const nxt of neighbours(node)) {
if (!seen.has(nxt)) { seen.add(nxt); q.push(nxt); }
}
}
}
return -1;let mut q = VecDeque::from([start]);
let mut seen = vec![false; n];
seen[start] = true; // mark on entry, not on exit
let mut dist = 0;
while !q.is_empty() {
for _ in 0..q.len() { // one ring: distance dist
let node = q.pop_front().unwrap();
if is_goal(node) { return dist; }
for &nxt in &adj[node] {
if !seen[nxt] { seen[nxt] = true; q.push_back(nxt); }
}
}
dist += 1;
}
-1import qualified Data.Set as S
-- Breadth-first search by rings: each step replaces the frontier by its unseen neighbours.
bfs :: Int -> Maybe Int
bfs start = go 0 [start] (S.singleton start)
where
go _ [] _ = Nothing
go d frontier seen
| any isGoal frontier = Just d
| otherwise =
let next = S.toList (S.fromList [n | v <- frontier, n <- neighbours v] S.\\ seen)
in go (d + 1) next (foldr S.insert seen next)
Problems
Count the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Easy12 minDoes an undirected graph contain a cycle?
Return true if the graph has a cycle: a search that meets a visited node by any edge but the one it came along has found one.
Open the problem → Easy8 minAdjacency matrix to adjacency list
Turn an n × n matrix of 0s and 1s into one list of neighbours per node: the conversion behind most graph solutions.
Open the problem → Easy10 minRepaint a region of an image
Change the colour of every pixel connected to a starting pixel, as a paint bucket does: the plainest depth-first search on a grid.
Open the problem → Easy12 minDepth-first order of a graph
Return the order in which a depth-first traversal first reaches every node, restarting on each part of a disconnected graph.
Open the problem → Easy10 minThe person everyone trusts
Find the one person who trusts nobody and is trusted by everyone else, by counting edges in and out of each node.
Open the problem → Easy12 minBreadth-first order of a graph
Return the order in which a breadth-first traversal reaches every node: nearest first, ties broken by number, every part of the graph covered.
Open the problem → Medium15 minCan the graph be split in two?
Return true if the nodes can be coloured with two colours so that every edge joins different colours. Colour while you search.
Open the problem → Medium15 minDistances between every pair of nodes
Return the matrix of shortest distances between every ordered pair of nodes, with −1 where no path exists.
Open the problem → Medium15 minThe connection that closes a loop
A tree had one extra edge added. Return the edge that can be removed to make it a tree again: the first one union-find cannot merge.
Open the problem → Medium18 minShortest path through a grid
Breadth-first search reaches every cell in the fewest moves because it explores in rings of equal distance. Why it beats depth-first search is the lesson.
Open the problem → Medium20 minEvery route from source to target
List every simple path from node 0 to node n − 1 in a directed graph. Search depth-first and take each node off the path on the way back.
Open the problem → Medium20 minShortest distances from one node
Return the shortest distance from a source to every node of a weighted directed graph: Dijkstra's algorithm with a heap.
Open the problem → Medium25 minPlace as many applicants as possible
Each applicant takes at most one job and each job one applicant. Return the largest number who can be placed, moving earlier choices when that helps.
Open the problem → Medium15 minSplit people who dislike each other
Put n people into two rooms so that no two people who dislike each other share one. A dislike is an edge, and the rooms are the two colours.
Open the problem → Medium15 minThe largest island
Return the number of cells in the largest island of a 0/1 grid: count each component's cells and keep the maximum.
Open the problem → Medium20 minDistance to the nearest zero
For every cell of a 0/1 grid, return the number of steps to the nearest 0. One breadth-first search that starts from all the zeros at once.
Open the problem → Medium20 minMoving cables to connect a network
Count the separate groups with a disjoint-set structure; spare cables are the edges that close a cycle.
Open the problem → Medium20 minRoutes with an exact total weight
Count the paths from source to destination, never repeating a node, whose edge weights add up to exactly the target.
Open the problem → Medium20 minThe city with the fewest neighbours within reach
For every city, count the others within a distance threshold, and return the city with the fewest. All-pairs distances make it a counting exercise.
Open the problem → Medium25 minThe path with the least effort
Cross a grid of heights from corner to corner so that the steepest single step is as gentle as possible. Dijkstra, with max in place of +.
Open the problem → Medium25 minTime for a signal to reach every node
Breadth-first search counts edges; when edges have travel times, a heap settles the nearest node first.
Open the problem → Medium15 minCount the friend groups
People know each other directly or through friends. Count the separate groups, given an n × n matrix of who knows whom.
Open the problem → Medium15 minDoes a directed graph contain a cycle?
Return true if following the arrows can bring you back to where you started. A node still on the current path is the giveaway.
Open the problem → Medium18 minMinutes until every orange rots
Start the search from every rotten orange at once. The answer is the number of rings it takes to reach the last fresh one.
Open the problem → Medium15 minThe cheapest node in each group
Each node has a value. Find the smallest value in every connected group and return their sum: the components template with f = min and g = sum.
Open the problem → Medium20 minShortest distances with negative edges
Return the shortest distance from a source to every node when weights can be negative, or report a negative cycle the source can reach.
Open the problem → Medium25 minConnect every point at the least cost
Join points on a grid with the least total Manhattan distance. Every pair is an edge, so the dense form of Prim's algorithm wins.
Open the problem → Medium25 minCheapest fare with at most k stops
Find the cheapest route that changes plane at most k times. Plain Dijkstra fails here; k + 1 rounds of Bellman-Ford do not.
Open the problem → Medium20 minCan every course be finished?
Dependencies form a directed graph, satisfiable exactly when it has no cycle. Kahn's algorithm takes courses with no prerequisites left until it runs out.
Open the problem → Medium20 minAn order to take every course
Return the smallest order in which every course can be taken, or an empty list if prerequisites form a cycle.
Open the problem → Medium20 minCapture the enclosed regions
Turn every region of O cells that cannot reach the edge of the board into X. The trick is to search from the border inwards.
Open the problem → Hard30 minMaximum flow through a network
Return the most that can be sent from a source to a sink when every directed edge has a capacity: augmenting paths with reverse edges.
Open the problem → Hard30 minRoutes that visit every node once
Count the paths from source to destination that visit every node exactly once. Backtracking works for small n; a bitmask over visited nodes makes it O(2ⁿ · n²).
Open the problem → Hard30 minSwim in rising water
Water rises one unit per second and a cell can be entered once it is covered. Find the earliest time the far corner can be reached.
Open the problem → Hard30 minShortest word transformation
Change one letter at a time, using only dictionary words, from a start word to an end word. Each word is a node; breadth-first search finds the shortest ladder.
Open the problem → Hard30 minShortest path with walls you may break
Reach the far corner of a grid in the fewest steps, removing at most k walls. The node is not the cell but the pair (cell, walls still allowed).
Open the problem →Or sit a timed interview: a fixed window, limited submissions, and a report at the end.