Home
Day 6 · Week 2

Bellman-Ford & 0-1 BFS

Level 2 · Shortest Path  ·  2 hrs: ~45 min theory · ~60 min code · ~15 min notes

🧠 Mental Model

Week 1's BFS gives shortest paths only when every edge costs the same. The moment edges carry different weights, "fewest edges" ≠ "cheapest path." Bellman-Ford answers the weighted single-source shortest-path (SSSP) question by repeatedly relaxing every edge until distances stop improving — and, uniquely among the classics, it tolerates negative edges and can detect negative cycles.

The one primitive is edge relaxation: for edge (u → v, w), if dist[u] + w < dist[v], we found a cheaper route to v, so update it. Every shortest-path algorithm is "just" a strategy for which order to relax edges. Bellman-Ford's strategy is the simplest possible: relax all of them, V−1 times.

⚙️ Mechanics

  1. Initialize dist[src] = 0, all others = +∞.
  2. Repeat V−1 times: relax every edge once.
  3. One extra pass: if any edge still relaxes, a negative cycle is reachable — report it.

Why V−1 passes? (the key insight)

Invariant: after k full passes, dist[v] holds the cost of the shortest path from the source to v that uses at most k edges.

A shortest path in a graph with no negative cycles is simple (no repeated vertices), so it has at most V−1 edges. Hence V−1 passes suffice to propagate every shortest distance to convergence.

📐 Correctness

Theorem (BF correctness) If the graph contains no negative-weight cycle reachable from the source, then after V−1 passes, dist[v] = δ(src, v) for every vertex v, where δ is the true shortest-path distance.
Proof (by the path-relaxation property) Let the shortest path to v be p = ⟨v₀=src, v₁, …, v_k=v⟩ with k ≤ V−1 edges. We show by induction that dist[v_i] = δ(src, v_i) after pass i.

Base: dist[v₀] = 0 = δ(src, src) before any pass.
Step: assume dist[v_{i−1}] is correct after pass i−1. Pass i relaxes all edges, including (v_{i−1} → v_i). That relaxation sets dist[v_i] ≤ dist[v_{i−1}] + w = δ(src, v_i). Since distances never drop below the true optimum, equality holds.

As k ≤ V−1, all vertices converge within V−1 passes.

Negative-cycle detection. If a V-th relaxation pass still improves some dist[v], then a path with ≥ V edges beats every simple path — impossible unless a reachable negative cycle exists. This is why the extra pass is a detector, not just a formality.

📊 Complexity & Positioning

AlgorithmTimeNeg. edges?Neg. cycle?Use when
Bellman-FordO(V·E)YesDetectsNegative weights present, or bounded-hop paths
DijkstraO((V+E) log V)NoNon-negative weights (Day 7)
0-1 BFSO(V + E)Only 0/1 weightsEvery edge weight ∈ {0, 1}
SPFA note: the queue-based "SPFA" optimization of Bellman-Ford is often faster in practice but has the same O(V·E) worst case. Mention it in interviews, don't rely on it.

💡 0-1 BFS — the special case

When every edge weight is 0 or 1, you don't need a heap. Use a deque: relax a weight-0 edge by pushing the neighbor to the front (same "layer"), a weight-1 edge by pushing to the back (next layer). The deque stays sorted by distance automatically, giving Dijkstra-quality results in linear time.

Why it works: the deque holds at most two distinct distance values at any time (d at the front, d+1 at the back). Front-insertion for 0-edges preserves that monotonicity — the same invariant that makes plain BFS correct, extended to a two-level frontier.

💻 Reference Implementation (Java)

Bellman-Ford with negative-cycle detection, then 0-1 BFS on a grid.

import java.util.*;

/** Bellman-Ford. Returns dist[]; null if a negative cycle is reachable. */
public long[] bellmanFord(int n, int[][] edges, int src) {
    long[] dist = new long[n];
    Arrays.fill(dist, Long.MAX_VALUE);
    dist[src] = 0;

    for (int pass = 1; pass < n; pass++) {          // V-1 passes
        for (int[] e : edges) {                        // relax every edge
            int u = e[0], v = e[1], w = e[2];
            if (dist[u] != Long.MAX_VALUE && dist[u] + w < dist[v])
                dist[v] = dist[u] + w;
        }
    }
    for (int[] e : edges) {                            // V-th pass = detector
        int u = e[0], v = e[1], w = e[2];
        if (dist[u] != Long.MAX_VALUE && dist[u] + w < dist[v])
            return null;                             // negative cycle
    }
    return dist;
}

/** 0-1 BFS: shortest path when edge weights are all 0 or 1. */
public int zeroOneBfs(List<int[]>[] adj, int n, int src, int dst) {
    int[] dist = new int[n];
    Arrays.fill(dist, Integer.MAX_VALUE);
    dist[src] = 0;
    Deque<Integer> dq = new ArrayDeque<>();
    dq.offerFirst(src);

    while (!dq.isEmpty()) {
        int u = dq.pollFirst();
        for (int[] e : adj[u]) {
            int v = e[0], w = e[1];   // w is 0 or 1
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                if (w == 0) dq.offerFirst(v);  // same layer → front
                else        dq.offerLast(v);   // next layer → back
            }
        }
    }
    return dist[dst];
}

⚠️ Common Pitfalls

Integer overflow: dist[u] + w with dist[u] = INT_MAX wraps negative. Guard with the != MAX_VALUE check or use long.
Stopping at V passes for the answer: the V-th pass is only for detection. Distances are final after V−1.
0-1 BFS without the dist-guard: a node can enter the deque multiple times; only act when you strictly improve dist[v], or you'll process stale entries.
Reporting a negative cycle that isn't reachable: only cycles reachable from src affect the answer; the guard on dist[u] != ∞ handles this.

🎭 Problem Variances — Bellman-Ford & 0-1 BFS in disguise

The tell for Bellman-Ford is negative weights, or a hard cap on the number of edges/hops. For 0-1 BFS it's every move costs 0 or 1.

Disguise / phrasingWhat's really askedTwist to handle
"Cheapest flight within K stops"Bounded-hop shortest pathRun exactly K+1 relaxation passes; copy dist each pass
"Detect if profit can grow unboundedly" (arbitrage)Negative-cycle detectionTake logs → negative cycle = arbitrage; V-th pass check
"Prices with rebates / negative costs"SSSP with negative edgesDijkstra invalid → Bellman-Ford
"Free moves vs costly moves"0-1 weighted shortest pathDeque: 0-edge push front, 1-edge push back
"Min obstacles/walls to remove to reach exit"0-1 BFS on a gridEmpty cell = 0, wall = 1
"Min direction changes / rotations"0-1 BFSSame direction = 0, turn = 1 (cf. LC 1368)
Routing rule: non-negative weights → Dijkstra (faster). Negative edges or bounded hops → Bellman-Ford. All weights ∈ {0,1} → 0-1 BFS (linear time, no heap).

🎯 Problems

LC 787 Cheapest Flights Within K Stops Bounded-hop shortest path — run exactly K+1 relaxation passes (the invariant is the point). Med
LC 743 Network Delay Time Solve with Bellman-Ford, then compare against Dijkstra tomorrow. Med
LC 1368 Minimum Cost to Make at Least One Valid Path in a Grid Keeping the arrow = weight 0, turning = weight 1 → textbook 0-1 BFS. Hard

📚 References