Graphs

Shortest Path Algorithms

Find the minimum-cost route between vertices. Unweighted โ†’ BFS in O(V+E). Non-negative weights โ†’ Dijkstra in O((V+E) log V). Negative edges โ†’ Bellman-Ford in O(VE). Three algorithms, three constraints, one goal.

Foundations ยท Array ยท HashMap ยท Heap ยท Graph ยท BFS ยท DFS ยท Topo Sort ยท Shortest Path ยท Sorting ยท Dynamic Prog.
01
Section One ยท Foundation

What is Shortest Path?

S A B C T 4 3 1 2 3 Sโ†’Bโ†’Cโ†’T = 6 Sโ†’Aโ†’Cโ†’T = 10 shortest: 6 โœ“

The shortest path problem asks: what is the minimum total edge weight from a source vertex to a destination (or to all other vertices)? The answer depends on the type of graph. Unweighted graphs: every edge has weight 1, so BFS finds the shortest path in O(V+E) โ€” the first time BFS reaches a vertex is the shortest distance. Weighted graphs with non-negative edges: Dijkstra's algorithm uses a priority queue to greedily process the closest unvisited vertex, running in O((V+E) log V) with a binary heap. Weighted graphs with negative edges: Bellman-Ford relaxes all edges V-1 times in O(VE), handling negative weights and detecting negative cycles. A fourth option โ€” Floyd-Warshall โ€” finds shortest paths between ALL pairs in O(Vยณ), useful for dense graphs and small V. In interviews, Dijkstra dominates. BFS is the foundation (covered in its own page), and Bellman-Ford appears in problems with negative weights or "at most K stops."

Analogy: GPS navigation. You want the fastest route from home to the airport. Each road has a travel time (weight). Dijkstra is the GPS: it explores the nearest intersection first, expands outward, and guarantees the first time it reaches the airport is via the fastest route. If some roads have "negative time" (impossible in real life, but useful in problems), Bellman-Ford handles it by checking all roads multiple times.
02
Section Two ยท Mental Model

How It Thinks

All edge weights are 1 (or equal)
โ–ถ
Use BFS โ€” the first visit to each vertex is the shortest path. No priority queue needed. O(V+E).
Edge weights are non-negative but vary
โ–ถ
Dijkstra: always process the vertex with the smallest tentative distance (greedy). Once a vertex is finalised, its distance is optimal. PriorityQueue gives O((V+E) log V).
Some edges have negative weights
โ–ถ
Dijkstra fails โ€” a "cheaper" path via a negative edge could appear later. Bellman-Ford relaxes ALL edges V-1 times to catch these. O(VE).
"Relax" an edge uโ†’v with weight w: if dist[u] + w < dist[v], update dist[v]
โ–ถ
Relaxation is the core operation of ALL shortest path algorithms. It says: "I found a shorter path to v โ€” going through u and then taking edge uโ†’v."
A negative cycle exists (total cycle weight < 0)
โ–ถ
Shortest path is undefined โ€” you can keep looping to get โˆ’โˆž. Bellman-Ford detects this: if any edge can still be relaxed after V-1 passes โ†’ negative cycle.
Need shortest path between ALL pairs of vertices
โ–ถ
Floyd-Warshall: 3 nested loops, O(Vยณ). Or run Dijkstra from each vertex: O(V(V+E) log V). Floyd is simpler for small, dense graphs.
03
Section Three ยท Unweighted

BFS โ€” Unweighted Shortest Path

When all edges have weight 1, BFS finds the shortest path naturally โ€” the first time it reaches a vertex is the minimum number of edges from the source. This is the simplest shortest path algorithm and the one you should reach for first in interviews. It works for unweighted graphs, grids ("minimum moves"), and word transformations ("Word Ladder").

Pseudocode
function bfsShortestPath(graph, src): dist = int[V], fill with โˆž dist[src] = 0 queue = [src] while queue not empty: u = queue.poll() for each neighbour v of u: if dist[v] == โˆž: // first visit = shortest dist[v] = dist[u] + 1 queue.add(v) return dist // O(V + E)
BFS shortest path is already covered in the BFS page. Use it for unweighted graphs. For weighted graphs, continue to Dijkstra below.
04
Section Four ยท Dijkstra

Dijkstra's Algorithm

Dijkstra's algorithm is BFS with a priority queue instead of a regular queue. Instead of processing vertices in FIFO order, it always processes the vertex with the smallest tentative distance. When a vertex is dequeued, its shortest distance is finalised โ€” this greedy property holds because all edge weights are non-negative (no future path can be shorter). The algorithm: initialise all distances to โˆž except source = 0. Add (0, source) to a min-heap. Repeatedly extract the minimum, and for each neighbour, if we can improve its distance, update and add to the heap.

Pseudocode โ€” Dijkstra
function dijkstra(graph, src): dist = int[V], fill with โˆž dist[src] = 0 pq = MinHeap() // (distance, vertex) pq.add(0, src) while pq not empty: (d, u) = pq.poll() // smallest distance if d > dist[u]: continue // stale entry โ€” skip for each (v, weight) in graph[u]: newDist = dist[u] + weight if newDist < dist[v]: // relaxation dist[v] = newDist pq.add(newDist, v) return dist // O((V+E) log V)
Dijkstra step-by-step on a 5-vertex graph
Edges: Sโ†’A(4), Sโ†’B(1), Aโ†’C(3), Bโ†’A(1), Bโ†’C(5), Cโ†’T(2) Init: dist=[0,โˆž,โˆž,โˆž,โˆž] pq=[(0,S)] Poll (0,S): relax Aโ†’4, Bโ†’1 โ†’ dist=[0,4,1,โˆž,โˆž] pq=[(1,B),(4,A)] Poll (1,B): relax Aโ†’2โœ“, Cโ†’6 โ†’ dist=[0,2,1,6,โˆž] pq=[(2,A),(4,A),(6,C)] Poll (2,A): relax Cโ†’5โœ“ โ†’ dist=[0,2,1,5,โˆž] pq=[(4,A),(5,C),(6,C)] Poll (4,A): stale (4>2) โ†’ skip Poll (5,C): relax Tโ†’7 โ†’ dist=[0,2,1,5,7] โ˜… Done dist = [S:0, A:2, B:1, C:5, T:7] shortest Sโ†’T = 7 via Sโ†’Bโ†’Aโ†’Cโ†’T Key: stale entry check if d > dist[u], this entry is outdated โ€” we already found a shorter path. Skip it.
Common Mistake: Forgetting the stale-entry check (if (d > dist[u]) continue;). Without it, you process the same vertex multiple times with outdated distances, potentially producing wrong answers and degrading to O(VE log V). This one line is essential.
05
Section Five ยท Bellman-Ford

Bellman-Ford Algorithm

Bellman-Ford handles negative edge weights by relaxing ALL edges V-1 times. Why V-1? The shortest path has at most V-1 edges โ€” so V-1 passes are enough to propagate the shortest distance through the longest possible path. After V-1 passes, do one more: if ANY edge can still be relaxed, a negative cycle exists. Bellman-Ford is slower than Dijkstra (O(VE) vs O((V+E) log V)) but more general.

Pseudocode โ€” Bellman-Ford
function bellmanFord(V, edges, src): dist = int[V], fill with โˆž dist[src] = 0 // V-1 passes โ€” relax all edges for i = 1 to V - 1: for each edge (u, v, w) in edges: if dist[u] != โˆž and dist[u] + w < dist[v]: dist[v] = dist[u] + w // relax // Negative cycle detection โ€” one more pass for each edge (u, v, w) in edges: if dist[u] != โˆž and dist[u] + w < dist[v]: return "negative cycle!" return dist // O(V ร— E)
Interview variant

Cheapest Flights Within K Stops (LC 787)

Modified Bellman-Ford: only do K+1 passes instead of V-1. At each pass, use the previous pass's distances (copy the array) to ensure you don't take more than K+1 edges. This is the classic "at most K stops" variant.

When Bellman-Ford wins

Negative Edges or Step Limits

  • Negative edge weights โ†’ Dijkstra fails
  • "At most K edges/stops" constraints
  • Negative cycle detection
  • Simpler code (no PriorityQueue)
06
Section Six ยท Comparison

Which Algorithm to Use

Algorithm Graph Type Negative Edges Time Space Use When
BFS Unweighted N/A O(V+E) O(V) All edges weight 1 (grids, word ladder)
Dijkstra Weighted, non-negative โœ— Fails O((V+E) log V) O(V) Most weighted graph problems
Bellman-Ford Weighted, any โœ“ Handles O(VE) O(V) Negative edges, K-stop limit
Floyd-Warshall All pairs โœ“ Handles O(Vยณ) O(Vยฒ) Small V, all-pairs shortest path
0-1 BFS Weights 0 or 1 only N/A O(V+E) O(V) Binary weights (use deque)
Decision flowchart:
  • Unweighted? โ†’ BFS. Weighted, non-negative? โ†’ Dijkstra. Negative edges? โ†’ Bellman-Ford. All pairs? โ†’ Floyd-Warshall.
  • Weights 0 or 1? โ†’ 0-1 BFS (deque). In interviews, 90% of shortest path problems are Dijkstra.
07
Section Seven ยท When to Use

Recognising Shortest Path Problems

Signal Algorithm Example
"minimum moves/steps" on unweighted graph or grid BFS Word Ladder (LC 127), Shortest Bridge (LC 934)
"minimum cost/time" with non-negative edge weights Dijkstra Network Delay Time (LC 743)
"cheapest flight with at most K stops" Bellman-Ford (K+1 passes) Cheapest Flights Within K Stops (LC 787)
"detect negative cycle" Bellman-Ford (V-th pass check) Negative weight cycle detection
"shortest path in grid with obstacles" BFS (unweighted) or Dijkstra (weighted) Shortest Path in Binary Matrix (LC 1091)
"path with minimum effort / maximum edge" Dijkstra (modify relaxation to track max edge) Path With Minimum Effort (LC 1631)
08
Section Eight ยท Implementation

Build It Once

Build Dijkstra once โ€” it covers most interview problems. The structure: adjacency list with weights, dist[] array, PriorityQueue of (distance, vertex) pairs, stale-entry skip.

Java โ€” Dijkstra (Network Delay Time)
// Network Delay Time โ€” LC 743 โ€” O((V+E) log V) int networkDelayTime(int[][] times, int n, int k) { List<List<int[]>> adj = new ArrayList<>(); for (int i = 0; i <= n; i++) adj.add(new ArrayList<>()); for (int[] t : times) adj.get(t[0]).add(new int[]{t[1], t[2]}); int[] dist = new int[n + 1]; Arrays.fill(dist, Integer.MAX_VALUE); dist[k] = 0; // min-heap: (distance, node) PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> a[0] - b[0]); pq.offer(new int[]{0, k}); while (!pq.isEmpty()) { int[] cur = pq.poll(); int d = cur[0], u = cur[1]; if (d > dist[u]) continue; // stale โ€” skip for (int[] e : adj.get(u)) { int v = e[0], w = e[1]; if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.offer(new int[]{dist[v], v}); } } } int max = 0; for (int i = 1; i <= n; i++) max = Math.max(max, dist[i]); return max == Integer.MAX_VALUE ? -1 : max; }
09
Section Nine ยท Java Reference

Use It In Java

IN JAVA โ€” Dijkstra Idioms
Adjacency list List<List<int[]>> adj โ€” each int[] is {neighbour, weight}
Min-heap PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> a[0]-b[0]);
Heap entry new int[]{distance, vertex} โ€” distance first for comparator
Pattern Java Code Note
Init distances Arrays.fill(dist, Integer.MAX_VALUE); โˆž sentinel โ€” guard against overflow when adding
Stale check if (d > dist[u]) continue; ESSENTIAL โ€” skip outdated heap entries
Relax edge if (dist[u] + w < dist[v]) Only update if strictly shorter
Add to heap pq.offer(new int[]{dist[v], v}); Lazy deletion โ€” don't remove old entries
BF copy array int[] prev = dist.clone(); For K-stops: use PREVIOUS pass's distances
โš  Gotcha: Integer.MAX_VALUE + anything overflows to a negative number, which is "less than" the current distance โ€” causing a wrong relaxation. Guard with: if (dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v]). Or use Integer.MAX_VALUE / 2 as the sentinel to avoid overflow entirely.
โš  Gotcha: Java's PriorityQueue does NOT support decrease-key. Instead, we use "lazy deletion" โ€” add a new entry with the updated distance and skip stale entries when polled. This is why the stale check (d > dist[u]) is critical.
โš  Gotcha: For Cheapest Flights (LC 787), you MUST clone the dist array (dist.clone()) at the start of each pass. Without cloning, a single pass can relax a chain of edges (using distances updated THIS pass), effectively using more than K stops.
10
Section Ten ยท Practice

Problems To Solve

Shortest path interviews test two things: (1) choosing the right algorithm (BFS vs Dijkstra vs Bellman-Ford), and (2) implementing Dijkstra cleanly with the stale-entry check. Practice Network Delay Time first โ€” it's the purest Dijkstra template โ€” then move to the variants.

Difficulty Pattern Problem Key Insight
Medium dijkstra Network Delay Time โ€” LC 743 Pure Dijkstra from source. Answer = max distance to any node. Return -1 if any node is unreachable.
Medium bellman-ford Cheapest Flights Within K Stops โ€” LC 787 Modified Bellman-Ford: K+1 passes with array cloning. Or BFS with level tracking. Or Dijkstra with (cost, node, stops) tuples.
Medium dijkstra Path With Minimum Effort โ€” LC 1631 Grid Dijkstra where "distance" = max absolute height difference along the path. Modified relaxation: max(dist[u], |h[u]-h[v]|).
Medium bfs Shortest Path in Binary Matrix โ€” LC 1091 BFS on a grid (8-directional). Unweighted = BFS is optimal. Return -1 if blocked.
Medium dijkstra Swim in Rising Water โ€” LC 778 Grid Dijkstra where cost = max elevation along the path. Modified relaxation: max(dist[u], grid[v]).
Hard dijkstra Shortest Path Visiting All Nodes โ€” LC 847 BFS/Dijkstra with bitmask state: (current node, visited set). State space = V ร— 2^V.
Interview Pattern:
  • See "minimum cost / minimum time / minimum effort"? โ†’ Dijkstra (non-negative weights) or Bellman-Ford (negative or K-stops).
  • See "minimum steps / minimum moves" on unweighted graph/grid? โ†’ BFS.
  • The template: dist[] + PriorityQueue + stale check + relaxation.
  • After building it once, every shortest path problem is a variation of the same 15 lines.

โ†’ See BFS for unweighted shortest path