Shortest Paths
When edges have weights, the fewest hops is not the cheapest route. Dijkstra, Bellman–Ford and friends fix that.
01 · why it exists
Why this exists, and the weight it carries
since1956 (idea) · 1959 (paper)
Dijkstra's algorithm
Edsger W. Dijkstra
In 1956 Dijkstra needed a demo for the new ARMAC computer in Amsterdam that non-mathematicians could follow: the shortest route between two Dutch cities, on a reduced map of 64 cities. As he recalled in a 2001 interview, he designed it in about 20 minutes on a café terrace in Amsterdam, resting during a shopping trip with his fiancée — without pencil and paper. He published it three years later, in 1959, as "A note on two problems in connexion with graphs". Heap versions came later (O(E log V) by 1972; Fredman and Tarjan's Fibonacci heap in 1987) — that's what makes it fast on huge sparse graphs.
the problem it solvedThe cheapest route from one place to every other place when every link has a non-negative cost — in time only a little more than the size of the map.
its jobs today
- Internet routing: an OSPF router computes its shortest-path tree with itself as the root using Dijkstra (RFC 2328, §16.1).
- Maps and games: route planners and A path-finding are Dijkstra with extras — A adds a "distance left" hint to the heap order.
- Any "cheapest / fastest / most reliable" question on a network with non-negative costs — including the minimax and probability variants in this chapter.
the weight it carries
Illustrative: a road map with 10 lakh junctions (V = 10⁶) and 25 lakh roads (E = 2.5·10⁶). Heap Dijkstra ≈ (V + E) · log₂ V ≈ 3.5·10⁶ × 20 = 7·10⁷ steps. Bellman–Ford: V · E = 2.5·10¹² relaxations — about 36,000× more.
without it
Routing would fall back to Bellman–Ford's V·E work, or to trying routes one by one — in the worst case a single city-to-city query on a big map could take hours instead of moments.
when · why · how
- when
- A weighted graph, every weight ≥ 0, one source (or one source–target pair).
- why
- The closest unsettled node can't be improved later: every other route already costs at least as much, and edges never subtract. O((V + E) log V) with a binary heap, versus O(V · E) for Bellman–Ford.
- how
PriorityQueue<int[]>of {dist, node} compared withInteger.compare; poll, skip whend > dist[u], relax, push. Switch tolong[]when sums can pass 2·10⁹.
since1955–1959
Bellman–Ford (edge relaxation in rounds)
Alfonso Shimbel; Lester R. Ford Jr.; Richard Bellman; Edward F. Moore
The method was found several times within a few years. Shimbel described it in 1955 with matrix-style "min-sum" arithmetic. Ford's 1956 RAND report gave the relaxation rule: whenever d(v) > d(u) + w, lower d(v). Bellman's 1958 paper "On a routing problem" swept every edge in rounds, in dynamic-programming style. Moore, at Bell Labs, presented the same idea in 1957 (printed 1959) while thinking about routing telephone traffic.
the problem it solvedShortest routes when some costs are negative — and a way to tell when a negative loop makes "shortest" meaningless.
its jobs today
- RIP, one of the oldest internet routing protocols, is built on the Bellman–Ford (distance-vector) idea: routers keep improving their routes from their neighbours' tables (RFC 2453).
- Finance: with edge weight −log(rate), a negative cycle is a currency-arbitrage loop — Bellman–Ford finds it.
- "At most k edges" questions such as cheapest flights within k stops: stop after k + 1 rounds.
the weight it carries
Illustrative: 20 currencies with a rate for every ordered pair, E = 20 × 19 = 380 edges. Bellman–Ford: (20 − 1) × 380 = 7,220 relaxations, plus one extra pass of 380 to catch a negative cycle (an arbitrage loop).
without it
Single-source questions on graphs with negative edges would have no fast, safe method — Dijkstra can return wrong answers there, and the fallback would be Floyd–Warshall's O(V³) all-pairs work — and catching an arbitrage-style negative cycle would cost O(V³) too.
when · why · how
- when
- Negative edge weights, detecting a negative cycle, or a limit on the number of edges.
- why
- Round i fixes every shortest path that uses ≤ i edges, so V − 1 rounds always suffice and an improving V-th round proves a negative cycle. Cost O(V · E) — the price of handling negatives.
- how
- A plain
int[][] edgeslist and oneint[] dist; checkdist[u] != Integer.MAX_VALUEbefore adding;dist.clone()every round when the edge count is limited.
since1959–1962
Floyd–Warshall (all pairs)
Bernard Roy; Stephen Warshall; Robert W. Floyd
Roy (1959, in French) and Warshall (January 1962) described the same triple loop for reachability: can i reach j at all? Floyd's one-page "Algorithm 97: Shortest path" in Communications of the ACM, June 1962, used it for distances. The whole algorithm is three nested loops — one of the shortest famous algorithms ever printed.
the problem it solvedDistances between every pair of points at once — negative edges included — with a few lines of code.
its jobs today
- Precomputing a full distance table for a small network — a few hundred data centres, cities or game waypoints — so every later lookup is O(1).
- Reachability for every pair (Warshall's form): "is course i a prerequisite of course j?" for all i, j.
- Spotting negative cycles: after the loops,
dist[i][i] < 0.
the weight it carries
Illustrative: 400 data centres. Floyd–Warshall: 400³ = 6.4·10⁷ steps and a 400 × 400 int matrix (1.6·10⁵ cells × 4 bytes = 640 KB). Dijkstra from all 400 on a sparse graph with E = 2,000: 400 × 2,400 × log₂ 400 ≈ 8.3·10⁶ — fewer steps, more code.
without it
Every all-pairs question would need V separate single-source runs — more code, more bugs, and slower on dense graphs.
when · why · how
- when
- All pairs with V up to a few hundred, dense graphs, or a reachability table.
- why
- Round k allows node k as a middle stop, so after V rounds every pair is final: O(V³) time, O(V²) space, no heap and no adjacency list.
- how
int[][] distfilled with a 10⁹ sentinel, 0 on the diagonal; loopsfor k, for i, for jwith k outermost; skip whendist[i][k]is the sentinel.
since?
0-1 BFS (deque instead of heap)
No single inventor: it is a folklore special case of Dijkstra with only two distance levels in play. Dial's 1969 bucket queue (CACM Algorithm 360) is the general version for small integer weights; the deque form spread through competitive programming.
its jobs today
- Grid puzzles and games where some moves are free and some cost one: breaking a wall, flipping an arrow, removing an obstacle.
- Fewest line changes on a metro map: riding along a line costs 0, switching lines costs 1.
the weight it carries
Illustrative: a 1000 × 1000 grid, V = 10⁶ cells and E ≈ 4·10⁶ moves. 0-1 BFS: about V + E = 5·10⁶ deque operations. Heap Dijkstra: about 4·10⁶ pushes × log₂(4·10⁶) ≈ 22 = 8.8·10⁷ — roughly 18× more.
without it
You'd pay a heap's log factor for nothing on every 0/1 problem.
when · why · how
- when
- Every edge costs exactly 0 or 1.
- why
- The deque only ever holds distances D and D + 1, in order, so its front is always the minimum — Dijkstra's guarantee in O(V + E) instead of O((V + E) log V).
- how
ArrayDeque:offerFirstfor 0-edges,offerLastfor 1-edges, alwayspollFirst; relax only when the distance improves.
since1960
Minimax (bottleneck) paths
Maurice Pollack
In 1960 Maurice Pollack, at the Stanford Research Institute, posed the "maximum capacity" route problem in a short letter to Operations Research. His setting was automatic teletype networks, where each origin–destination pair used a single route. The route judged by its weakest link is today called the widest or bottleneck path; swap max and min and you get this chapter's minimax paths.
the problem it solvedChoosing a route by its worst (or weakest) link instead of its total length.
its jobs today
- Networking: the widest path — the route whose slowest link is as fast as possible — bounds the bandwidth one connection can get.
- Height maps: the smallest worst climb (LC 1631) or the lowest water level that joins two corners (LC 778).
- Kruskal-style union-find answers the same question: add edges from cheapest to costliest until the two ends connect.
the weight it carries
Illustrative: a 100 × 100 height map (LC 1631's limit): 10⁴ cells, ≈ 4·10⁴ moves. Minimax Dijkstra ≈ 4·10⁴ × log₂(4·10⁴) ≈ 4·10⁴ × 15.3 ≈ 6·10⁵ steps. Binary search + BFS: ⌈log₂ 10⁶⌉ = 20 runs × 5·10⁴ = 10⁶.
without it
Summing the steps answers the wrong question, and trying every path is exponential.
when · why · how
- when
- A path is judged by its single worst step (max) or weakest link (min), not by its total.
- why
- max never decreases along a path, so Dijkstra's "polled = final" argument still holds: O(E log V). Binary search + BFS costs O(E · log range); union-find on sorted edges costs O(E log E).
- how
- The Dijkstra template with
next = Math.max(e, step)in place ofd + w; return when the goal is polled.
02 · plain english
The idea in 30 seconds
BFS finds the path with the fewest edges. With weights, a 3-edge route can be cheaper than a 1-edge route, so BFS gives wrong answers.
Dijkstra fixes this by always expanding the closest unfinished node next — a priority queue replaces BFS's plain queue.
Negative weights break Dijkstra's promise; Bellman–Ford handles them by relaxing every edge, round after round.
picture this
A packet leaves your laptop in Kolkata for a server in Mumbai, and every router-to-router link has a cost. An OSPF router computes routes exactly like Dijkstra: settle the cheapest router first, then use it to reach the next cheapest. Costs never go negative, so a settled cost can never be beaten later.
03 · remember
What you must know
- Relaxation is the one move every algorithm here shares:
if (dist[u] + w < dist[v]) dist[v] = dist[u] + w;. They only differ in which order they relax edges. - Dijkstra (weights ≥ 0): poll the smallest
{dist, node}from aPriorityQueue; that distance is final. O((V + E) log V). - Lazy deletion: Java's
PriorityQueuecan't lower a key, so push a new entry and skip stale ones withif (d > dist[u]) continue;. - Bellman–Ford (negative weights OK): relax all E edges, V − 1 times. If a V-th round still improves something, there is a negative cycle. O(V·E).
- Bellman–Ford with k rounds = cheapest path using at most k edges (copy the array each round). That's exactly LC 787.
- Floyd–Warshall: all pairs in O(V³) with three loops — the middle stop
kmust be the outer loop. - 0-1 BFS: weights are only 0 or 1 → use an
ArrayDeque, 0-edges to the front, 1-edges to the back. O(V + E), no heap. - Many grid problems are shortest paths in disguise. If the "cost" of a path is its max step (not the sum), Dijkstra still works — it's a minimax path.
04 · see it
Play with it in 3D
Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.
Dijkstra step by step
Watch the priority queue: the smallest entry is polled, its edges relaxed, and stale entries skipped. Green edges form the shortest-path tree.
Bellman–Ford relaxation rounds
The default graph has negative edges. Each round fixes paths one edge longer; a quiet round means we can stop early.
Floyd–Warshall matrix
Each round allows one more node as a middle stop. The violet row and column supply every candidate path in that round.
0-1 BFS on a grid
Free cells jump to the front of the deque, obstacles go to the back. The deque stays sorted without a heap.
Minimax Dijkstra on a height map
Same heap, new combine step: a route costs its worst climb, max(e, step), not the sum. The goal's first poll is the answer.
05 · cost
Time & space, explained
| Time | Space | Weights | why | |
|---|---|---|---|---|
| BFS | O(V + E) | O(V) | all equal | Each node and edge is handled once. Only correct when every edge costs the same. |
| 0-1 BFS (deque) | O(V + E) | O(V) | 0 or 1 | A node is pushed at most twice; the deque never needs sorting. |
| Dijkstra, binary heap (lazy) | O((V + E) log V) | O(V + E) | ≥ 0 | Up to E pushes and polls, each O(log E) = O(log V) because E ≤ V². |
| Dijkstra, plain array | O(V²) | O(V) | ≥ 0 | V rounds, each scans all V nodes for the minimum. Wins on dense graphs (E ≈ V²). |
| Bellman–Ford | O(V · E) | O(V) | any (detects negative cycles) | V − 1 rounds × E relaxations. Early exit when a round changes nothing. |
| Bellman–Ford, k rounds | O(k · (V + E)) | O(V) | any | k rounds, each relaxing all E edges and copying dist (O(V)) so a round can't chain more than one new edge. |
| Time | Space | why | |
|---|---|---|---|
| Floyd–Warshall | O(V³) | O(V²) | Three nested loops over V; one V × V matrix updated in place. |
| Dijkstra from every node | O(V · (V + E) log V) | O(V + E) | V independent Dijkstra runs. Beats Floyd on sparse graphs. |
| Path reconstruction | O(path length) | O(V) | Store parent[v] when relaxing; walk back from the target and reverse. |
| Binary search on answer + BFS (grids) | O(mn · log R) | O(mn) | log R guesses (R = value range), each a full O(mn) BFS. |
06 · java
Raw vs Collections Framework
Dijkstra: plain arrays vs PriorityQueue
class DijkstraArray { /** O(V²) Dijkstra with plain arrays: no heap, scan for the closest unfinished node. */ static int[] shortest(int n, int[][] edges, int src) { int[][] w = new int[n][n]; for (int[] row : w) Arrays.fill(row, -1); // -1 = no edge for (int[] e : edges) if (w[e[0]][e[1]] == -1 || e[2] < w[e[0]][e[1]]) w[e[0]][e[1]] = e[2]; int[] dist = new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); boolean[] done = new boolean[n]; dist[src] = 0; for (int round = 0; round < n; round++) { int u = -1; // closest unfinished node for (int v = 0; v < n; v++) if (!done[v] && dist[v] != Integer.MAX_VALUE && (u == -1 || dist[v] < dist[u])) u = v; if (u == -1) break; // the rest is unreachable done[u] = true; // dist[u] is now final for (int v = 0; v < n; v++) if (w[u][v] >= 0 && dist[u] + w[u][v] < dist[v]) dist[v] = dist[u] + w[u][v]; } return dist; }}class Dijkstra { /** edges[i] = {u, v, w}: directed, w ≥ 0. Unreachable nodes keep Integer.MAX_VALUE. */ static int[] shortest(int n, int[][] edges, int src) { List<List<int[]>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) adj.add(new ArrayList<>()); for (int[] e : edges) adj.get(e[0]).add(new int[] {e[1], e[2]}); int[] dist = new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); dist[src] = 0; // Entries are {distance, node}; the smallest distance comes out first. PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0])); pq.offer(new int[] {0, src}); while (!pq.isEmpty()) { int[] top = pq.poll(); int d = top[0], u = top[1]; if (d > dist[u]) continue; for (int[] next : adj.get(u)) { int v = next[0], w = next[1]; if (d + w < dist[v]) { dist[v] = d + w; pq.offer(new int[] {dist[v], v}); } } } return dist; }}- The heap version is the default interview answer: O((V + E) log V) on sparse graphs.
- Store
int[] {dist, node}in the heap, compare withInteger.compare(a[0], b[0])— nevera[0] - b[0], which can overflow. PriorityQueuehas no decrease-key. Push a fresh entry instead and skip it later whend > dist[u](lazy deletion).- The array version needs no heap at all and is O(V²) — better when the graph is dense, or V is tiny.
- Use
longdistances when weights × path length can pass ~2.1·10⁹ (LC 1976 has weights up to 10⁹).
Rebuild the actual path
class PathReconstruction { /** Dijkstra that also remembers parent[v] = the node we came from; then walks back. */ static List<Integer> path(int n, int[][] edges, int src, int target) { List<List<int[]>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) adj.add(new ArrayList<>()); for (int[] e : edges) adj.get(e[0]).add(new int[] {e[1], e[2]}); int[] dist = new int[n]; int[] parent = new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); Arrays.fill(parent, -1); dist[src] = 0; PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0])); pq.offer(new int[] {0, src}); while (!pq.isEmpty()) { int[] top = pq.poll(); int d = top[0], u = top[1]; if (d > dist[u]) continue; for (int[] next : adj.get(u)) { if (d + next[1] < dist[next[0]]) { dist[next[0]] = d + next[1]; parent[next[0]] = u; // best way into next[0] is via u pq.offer(new int[] {dist[next[0]], next[0]}); } } } if (dist[target] == Integer.MAX_VALUE) return List.of(); // unreachable List<Integer> path = new ArrayList<>(); for (int v = target; v != -1; v = parent[v]) path.add(v); // target → … → src Collections.reverse(path); return path; }}- Whenever
dist[v]improves, setparent[v] = u. The parents form a tree rooted at the source. - Walk from the target to the source through
parent, thenCollections.reverse— O(path length). - If
dist[target]is still ∞, there is no path: return an empty list.
Bellman–Ford (negative edges, negative cycles)
class BellmanFord { /** Works with negative edges. Returns null if a negative cycle is reachable from src. */ static int[] shortest(int n, int[][] edges, int src) { int[] dist = new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); dist[src] = 0; for (int round = 1; round <= n - 1; round++) { boolean changed = false; for (int[] e : edges) { int u = e[0], v = e[1], w = e[2]; if (dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v]) { dist[v] = dist[u] + w; changed = true; } } if (!changed) break; } for (int[] e : edges) if (dist[e[0]] != Integer.MAX_VALUE && dist[e[0]] + e[2] < dist[e[1]]) return null; return dist; }}- Just an edge list — no adjacency list needed.
int[][] edgesis enough. - Guard
dist[u] != Integer.MAX_VALUEbefore adding, orMAX_VALUE + woverflows into a negative number. - Only negative cycles reachable from the source are detected. That's usually what you want.
Floyd–Warshall (all pairs)
class FloydWarshall { static final int INF = 1_000_000_000; // "no path yet" — never added to, see the guards /** All-pairs shortest paths (negative edges OK). dist[i][i] < 0 afterwards ⇔ i is on a negative cycle. */ static int[][] allPairs(int n, int[][] edges) { int[][] dist = new int[n][n]; for (int[] row : dist) Arrays.fill(row, INF); for (int i = 0; i < n; i++) dist[i][i] = 0; for (int[] e : edges) dist[e[0]][e[1]] = Math.min(dist[e[0]][e[1]], e[2]); for (int k = 0; k < n; k++) for (int i = 0; i < n; i++) { if (dist[i][k] == INF) continue; // i can't reach k for (int j = 0; j < n; j++) if (dist[k][j] != INF && dist[i][k] + dist[k][j] < dist[i][j]) dist[i][j] = dist[i][k] + dist[k][j]; } return dist; }}- Use a big sentinel like
1_000_000_000and skip ∞ cells, so ∞ + negative never fakes a path. - After the loops,
dist[i][i] < 0means node i sits on a negative cycle. - Practical up to V ≈ 400 (64 million steps). For bigger sparse graphs, run Dijkstra from each node.
0-1 BFS with ArrayDeque
class ZeroOneBfs { /** Every weight is 0 or 1: a deque replaces the heap. 0-edges go to the front, 1-edges to the back. */ static int[] shortest(int n, int[][] edges, int src) { List<List<int[]>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) adj.add(new ArrayList<>()); for (int[] e : edges) adj.get(e[0]).add(new int[] {e[1], e[2]}); int[] dist = new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); dist[src] = 0; Deque<Integer> deque = new ArrayDeque<>(); deque.offerFirst(src); while (!deque.isEmpty()) { int u = deque.pollFirst(); for (int[] next : adj.get(u)) { int v = next[0], w = next[1]; if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; if (w == 0) deque.offerFirst(v); // same distance → front else deque.offerLast(v); // one more → back } } } return dist; }}offerFirstfor 0-weight edges,offerLastfor 1-weight edges, alwayspollFirst.- The deque only ever holds two distance values, D and D + 1, in order — so the front is always the minimum.
- Use
ArrayDeque, notLinkedList(faster) and neverStack.
| call | does | cost |
|---|---|---|
new PriorityQueue<int[]>((a, b) -> Integer.compare(a[0], b[0])) | Min-heap of {dist, node} pairs | O(1) to create |
pq.offer(e) / pq.poll() | Push / remove the smallest | O(log n) |
pq.peek() | Look at the smallest without removing | O(1) |
pq.remove(obj) | Remove an arbitrary entry (avoid — that's why we use lazy deletion) | O(n) |
deque.offerFirst(x) / offerLast(x) / pollFirst() | 0-1 BFS operations on ArrayDeque | O(1) amortized |
Arrays.fill(dist, Integer.MAX_VALUE) | Mark every node unreached | O(V) |
dist.clone() | Snapshot for round-limited Bellman–Ford | O(V) |
Comparator.comparingInt(a -> a[0]) | Same comparator, more readable | O(1) |
Unweighted graph / every edge costs the same
→ Plain BFS with ArrayDeque
O(V + E); a heap would only add a log factor.
Weights are only 0 and 1
→ 0-1 BFS with ArrayDeque
O(V + E) and no heap.
Non-negative weights, one source
→ Dijkstra with PriorityQueue<int[]>
O((V + E) log V) — the workhorse.
Negative edges, or "at most k edges"
→ Bellman–Ford (copy the array each round for the k-limit)
Dijkstra's "polled = final" promise breaks with negative edges.
Distances between all pairs, V ≤ ~400
→ Floyd–Warshall on int[V][V]
Tiny code, O(V³), handles negative edges.
Path cost = max (or min) edge, not sum
→ Dijkstra with max() instead of +, or binary search + BFS, or union-find
Minimax paths still have the "finalized when polled" property.
07 · recognise
Spot the pattern
"Minimum cost / time / distance" with weighted edges
Dijkstra (if no negative weights)
"At most k stops / k edges"
Bellman–Ford with k + 1 rounds, or BFS by levels
Negative weights, or "detect an arbitrage / negative cycle"
Bellman–Ford (or Floyd–Warshall's diagonal)
"Every pair", "for each city count reachable cities", n ≤ 100
Floyd–Warshall
Costs are 0 or 1 ("free if you follow the arrow", "remove an obstacle")
0-1 BFS with a deque
Grid where a path's cost is its largest step / highest cell
Minimax Dijkstra, or binary search the answer + BFS, or union-find
Maximise a product of probabilities ≤ 1
Dijkstra with a max-heap (multiplying by ≤ 1 never helps, like adding ≥ 0)
"Number of shortest paths"
Dijkstra + a ways[] array updated on ties
08 · traps & tricks
Mistakes to avoid, tricks to use
traps
- Running Dijkstra with negative edges. The visited-set version silently returns wrong answers; the lazy version loses its time guarantee.
- Forgetting the stale check
if (d > dist[u]) continue;— still correct, but the run can blow up to far more work. - Comparator
(a, b) -> a[0] - b[0]overflows on large values. UseInteger.compare/Long.compare. Integer.MAX_VALUE + wwraps to a negative number. Check for ∞ before adding, or use a smaller sentinel like 10⁹.- Floyd–Warshall with
kas the inner loop — the results are wrong.k(the middle stop) must be the outermost loop. - LC 787 with plain Bellman–Ford updating in place: one round can chain several edges, breaking the k-stop limit. Copy the array each round.
- Marking a node visited when it is pushed in standard Dijkstra. A cheaper route may arrive later; finalize on poll.
- Using
intwhen sums can exceed 2³¹ − 1 — LC 1976 needslong.
tricks
- Say "relax" out loud: "I relax edge u→v: if going through u is cheaper, update v and push it." Interviewers recognise it instantly.
- Before coding, ask: weighted? negative weights? one source or all pairs? limit on edges? The answers pick the algorithm.
- Memorise one Dijkstra template (the one in this chapter) and adapt it: the combine step (+, max, ×) and the heap order are the only knobs.
- Grids: treat each cell as a node
r * cols + cor keep{cost, r, c}arrays in the heap. 4 directions → E ≈ 4·V. - If the destination is known, return as soon as it is polled — not when it is first pushed.
- Chalo, dry-run karte hain — on a 4-node graph where the direct edge is expensive (0→3 costs 10, 0→1→2→3 costs 3). It catches most bugs.
09 · why & how
Questions you might be too shy to ask
Why can't I just use BFS on a weighted graph?
BFS finishes nodes in order of edge count. A direct road of length 100 is found before a 3-road route of total length 3, and BFS never revisits it. Dijkstra finishes nodes in order of total distance instead — that's the whole difference.
Why does the heap hold {dist, node} pairs and not just the node?
A PriorityQueue places an entry by its key at the moment it is pushed. If you push only node and compare by dist[node], that key changes later while the node sits inside — the heap does not re-sort itself, so poll() can hand you the wrong node. Storing the distance inside the entry freezes the key. That's also why an older, bigger copy can go stale and must be skipped.
Why does Dijkstra break with negative edges?
Dijkstra assumes that once the closest node is polled, no other route can be shorter — every other route already costs at least as much and can only grow. A negative edge lets a route shrink later. Example: 0→1 (2), 0→2 (5), 2→1 (−4), 1→3 (1). The visited-set version finalizes node 1 at 2 and node 3 at 3, but 0→2→1→3 costs 2. The lazy version may re-process nodes and patch this up, but then its O((V + E) log V) guarantee is gone (worst case exponential). Use Bellman–Ford.
What is a negative cycle, and why does it break "shortest"?
A loop whose edges add up to less than 0 — like 1→2 (3), 2→1 (−5): one round trip costs −2. Go around again and the route gets cheaper again, forever. So nothing reachable from that loop has a shortest route; the honest answer is "−∞". That's why Bellman–Ford reports the cycle instead of a number. Dhyan do: in an undirected graph, one negative edge u–v is already a negative cycle (u→v→u).
Can't I just add a big number to every edge so nothing is negative?
No — it punishes routes that use more edges. Example: 0→1 (−2) then 1→2 (−2) costs −4; the direct 0→2 (−3) costs −3. The 2-edge route wins. Add 3 to every edge: the 2-edge route becomes 1 + 1 = 2, the direct one 0. Now the wrong route wins. Each route pays +3 per edge, not +3 once, so the ranking of routes changes.
Why lazy deletion instead of updating the entry in the heap?
PriorityQueue.remove(obj) is O(n), and there is no decrease-key. Pushing a duplicate is O(log n). The old entry stays, and when it's polled we see d > dist[u] and skip it. The heap can hold about E entries, so each operation is O(log E) — and since E ≤ V², log E ≤ 2 log V, which is still O(log V).
Why must k be the outer loop in Floyd–Warshall?
Round k means "shortest paths that may only stop at nodes 0..k in the middle". Round k needs round k − 1 fully finished for every pair. With k inside, you would combine paths that aren't ready yet.
When should I pick Bellman–Ford over Dijkstra?
Three cases: negative edge weights, detecting a negative cycle, or a limit on the number of edges ("at most k stops"). Otherwise Dijkstra is much faster.
Why does 0-1 BFS work without a heap?
When you pop a node at distance D, a 0-edge creates distance D (goes to the front) and a 1-edge creates D + 1 (goes to the back). So the deque only ever contains D's followed by D + 1's — already sorted. The front is always the minimum, just like a heap would give you.
Swim in Rising Water marks a cell seen when it is pushed. Isn't that the pitfall?
It is safe there for one reason: the price of entering a cell is its own height — the same from every side. Polls come out in non-decreasing order, so a later route into that cell costs max(t', h) ≥ max(t, h), never less than the first push. In Path With Minimum Effort the step |Δh| depends on which neighbour you came from, so a later route can be cheaper. There, finalize on poll. Not sure? Use the safe template: relax, push, skip stale entries.
Can I find the longest path by putting a max-heap in Dijkstra?
No. A detour can always make a path longer, so "polled = final" breaks — the first time you settle a node is not its longest route. For a general graph, the longest simple path has no known fast algorithm (it is NP-hard). On a DAG it is easy: go in topological order and take max instead of min — see Topological Sort. Path with Maximum Probability is different: multiplying by ≤ 1 never helps, so Dijkstra's promise still holds.
Is Dijkstra greedy or dynamic programming?
Both views are fair. It greedily picks the closest node; the update dist[v] = min(dist[v], dist[u] + w) is a DP recurrence. Bellman–Ford is the pure DP version: when each round reads a copy of the previous round (as Cheapest Flights does), dist after k rounds = best path with ≤ k edges.
Do I need a visited[] array with Dijkstra?
Not with lazy deletion — the stale check d > dist[u] does the same job. Some people add visited[] and skip polled nodes; both are correct. Pick one and be consistent.
What is A*, and will they ask me for it?
A* is Dijkstra with a hint. The heap is ordered by dist[u] + h(u), where h never over-estimates the distance left (on a game map: the straight-line distance to the goal). It explores fewer nodes when the hint is good, and with h = 0 it is exactly Dijkstra. Interviews rarely need it beyond this explanation — Dijkstra is the must-know.
10 · practice
Interview problems
Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.
- #743
- #787
- #1514Path with Maximum ProbabilityMediumGoogle
- #1631
- #778Swim in Rising WaterHardMetaGoogleAmazon
- #1334
- #1368
- #2290
- #1976Number of Ways to Arrive at DestinationMediumGoogleMeta
Also practise this here
Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.
- #1091Shortest Path in Binary MatrixMediumin Graphs
The warm-up: every move costs the same, so plain BFS already is a shortest-path algorithm.
11 · revise
The whole chapter on one screen
9 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.
- what it is
Relax an edge:
if (dist[u] + w < dist[v]) dist[v] = dist[u] + w;. Every algorithm here is relaxation in a different order. - use it when
Weights ≥ 0 → Dijkstra. Negative or "at most k edges" → Bellman–Ford. All pairs, V ≤ ~400 → Floyd–Warshall. Only 0/1 → 0-1 BFS.
- template
Heap of {dist, node}: poll →
if (d > dist[u]) continue;→ relax every edge → push each improved {dist[v], v}. - cost
Dijkstra O((V + E) log V) · Bellman–Ford O(V·E) · Floyd O(V³) · 0-1 BFS O(V + E) · plain BFS O(V + E) when all edges cost the same.
- trap
Dijkstra + a negative edge = wrong answers. "Polled means final" only holds when no edge can shrink a path later.
- trap
(a, b) -> a[0] - b[0]can overflow andInteger.MAX_VALUE + wwraps negative. UseInteger.compare; check ∞ before adding. - trap
Floyd–Warshall: the middle stop
kis the OUTER loop. K-stops Bellman–Ford: clone the array each round. - pro tip
Path cost is a max (effort, water level) or a product ≤ 1? Same Dijkstra — swap
+formaxor×, keep the heap. - pro tip
Target known? Return when it is polled, not when it is first pushed.
12 · recall
Flip, remember, repeat
What does "relaxing" edge u → v mean?
Question 1: What does "relaxing" edge u → v mean?
13 · check yourself
Quick quiz
Edges 0→1 (10), 0→2 (1), 2→1 (1). What does BFS (fewest edges) give for node 1, and what is the true shortest distance?
Why does Dijkstra require non-negative weights?
You poll {7, node 3} but dist[3] is already 5. What do you do?
In Floyd–Warshall, which loop must be outermost?
After V − 1 rounds of Bellman–Ford, one more round still lowers a distance. What does it mean?
Edge weights are only 0 or 1. What's the cleanest O(V + E) method?
Cheapest Flights Within K Stops: why copy the cost array each Bellman–Ford round?
14 · learn more
A second teacher, if you want one
- ▶ videoG-32. Dijkstra's Algorithm - Using Priority Queue - C++ and Java - Part 1 ↗Striver (take U forward) · EnglishDijkstra with a PriorityQueue, coded in Java. Part 3 (G-34) explains why a plain queue isn't enough and derives the time complexity.
- ▶ videoShortest Path Algorithms (Dijkstra and Bellman-Ford) - Simplified ↗Abdul Bari · EnglishDijkstra and Bellman–Ford side by side on worked examples, in Abdul Bari's calm whiteboard style.
- ◈ visualizerSingle-Source Shortest Paths (Dijkstra/+ve Weighted, BFS/Unweighted, Bellman-Ford, DFS/Tree, Dynamic Programming/DAG) ↗VisuAlgo (Steven Halim, NUS) · EnglishRun BFS, Dijkstra and Bellman–Ford on the same weighted graph and watch every relaxation happen.