Graphs, No Gaps — Traversal & Connectivity · Ordering, Partitions & Spanning Structure · Weighted Paths Library
Show

PATTERN 3 — WEIGHTED PATHS & SEARCH ON THE ANSWER#

The machine: relax edges until no relaxation improves anything — or stop asking for the optimum and start asking whether a candidate is feasible.

3.1 Sub-variant map#

Sub-variantThe one idea
ADijkstra — the templateThe first pop of a node is final, and only if weights are non-negative
BDijkstra on implicit gridsThe graph is m × n cells; the relaxation is the parameter
CAugmented-state shortest pathdist is indexed by the whole state, not just the node
D0-1 BFSWeights in {0,1} — a deque replaces the heap and the log disappears
EBellman-FordNegative edges, or a cap on the number of edges used
FFloyd–Warshall & all-pairsn ≤ ~400, and the k loop is outermost
GBinary search on the answer + connectivityFeasibility is monotone in the threshold
HOffline queries + DSU sweepSort the queries too, and answer them all in one pass
I⚠︎ Anti-patterns — when shortest path is the wrong machineEach one runs the wrong algorithm until it visibly fails

3.2 Problems#

ADijkstra, the template#

A heap of (dist, node), lazy deletion, skip stale pops. The relaxation operator is the only thing that varies.

Solved#ProblemDiffSub-variantWhy it's essential
83743. Network Delay TimeMediumAThe atom. if (d > dist[u]) continue; is the lazy deletion — omit it and it still works but degrades
841514. Path with Maximum ProbabilityMediumAMax-heap, multiply instead of add. Dijkstra is still valid because products of values in [0,1] are monotone non-increasing — say why out loud
851976. Number of Ways to Arrive at DestinationMediumACounting paths during relaxation: < overwrites the count, == adds to it. The two branches are the problem
862642. Design Graph With Shortest Path CalculatorHardADijkstra as an API rather than a one-shot answer; edge additions between queries

BDijkstra on implicit grids#

Same template, but neighbours are computed and dist is a 2-D array. The relaxation is often not a sum.

Solved#ProblemDiffSub-variantWhy it's essential
87⚠︎1631. Path With Minimum EffortMediumBGrid DP is the wrong instinct: 4-directional movement means there is no evaluation order in which every predecessor is already computed. Bottleneck relaxation: `max(dist[u],
88778. Swim in Rising WaterHardBSame bottleneck shape, max(dist[u], grid[v]). Three valid solutions (Dijkstra, binary search + BFS, DSU by time) — know all three and when each is cleanest
89505. The Maze II PROMediumBThe edge is a roll; its weight is the roll length. Free substitute: 1631
902577. Minimum Time to Visit a Cell In a GridHardBThe parity wait: if you arrive too early you bounce between two cells, so add 0 or 1 to fix parity

CAugmented-state shortest path#

If two arrivals at the same node differ in anything that affects the future, they are different states.

Solved#ProblemDiffSub-variantWhy it's essential
911129. Shortest Path with Alternating ColorsMediumCThe state is (node, lastColour). The layered-graph idea in its smallest form
921786. Number of Restricted Paths From First to Last NodeMediumCTwo machines composed: Dijkstra to get dist[], then memoised DAG DP over the strictly-decreasing edges
931928. Minimum Cost to Reach Destination in TimeHardCdist[node][timeUsed] — a genuinely 2-D distance table
1293 / 864 (see §1.J)CThe unweighted versions of the same idea. Solve those first

D0-1 BFS#

Weights in {0,1}: push zero-cost neighbours to the front, cost-1 to the back. Correct for the same reason Dijkstra is, minus the heap.

Solved#ProblemDiffSub-variantWhy it's essential
941368. Minimum Cost to Make at Least One Valid Path in a GridHardDThe atom. Following the arrow costs 0, any other direction costs 1
952290. Minimum Obstacle Removal to Reach CornerHardDEmpty cell 0, obstacle 1. The transfer test for 1368 — if it takes more than ten minutes, redo 1368

EBellman-Ford#

Relax every edge, V−1 times. Slower than Dijkstra and strictly more general: negative weights, and edge-count constraints.

Solved#ProblemDiffSub-variantWhy it's essential
96⚠︎787. Cheapest Flights Within K StopsMediumEDijkstra is wrong here, not just awkward: with a stop budget the first pop of a node is no longer final — a cheap arrival with too many stops blocks an expensive arrival that could still finish. Bellman-Ford with k+1 rounds over a snapshot of the previous round's distances
743. Network Delay TimeMediumERe-solve #83 with Bellman-Ford; compare O(E log V) to O(V·E) on the given constraints

FFloyd–Warshall & all-pairs#

Three nested loops with k outermost. n ≤ ~400 in the constraints is the tell.

Solved#ProblemDiffSub-variantWhy it's essential
971334. Find the City With the Smallest Number of Neighbors at a Threshold DistanceMediumFThe template, and the tie-break on the largest index
98399. Evaluate DivisionMediumFMultiplicative relaxation on a weighted undirected graph. Solvable by DFS, Floyd, or weighted DSU — write at least two
991462. Course Schedule IVMediumFTransitive closure as boolean Floyd — reachability with the same three loops
1002101. Detonate the Maximum BombsMediumFBuild a directed graph from geometry (range is not symmetric), then reachability from each node

GBinary search on the answer + a connectivity check#

If feasible(x) is monotone, you never need the optimum — only a predicate. This is §3.G/H of Bundle 01 with BFS as the check.

Solved#ProblemDiffSub-variantWhy it's essential
1012812. Find the Safest Path in a GridMediumGMulti-source BFS to compute the safety field, then binary search the threshold with a BFS feasibility check. Two machines, cleanly separated
1021102. Path With Maximum Minimum Value PROMediumGMaximin path. Free substitute: 778
1631. Path With Minimum EffortMediumGSecond solve: binary search the effort, BFS over edges within it. Write the mapping to the Dijkstra version explicitly
778. Swim in Rising WaterHardGThird solve: DSU adding cells in elevation order until 0 and n²−1 connect

HOffline queries + DSU sweep#

Sorting the queries is allowed. Sort both, sweep once, and a per-query O(E) becomes one O(E α) pass.

Solved#ProblemDiffSub-variantWhy it's essential
1031697. Checking Existence of Edge Length Limited PathsHardHThe canonical form: sort edges by weight, sort queries by limit, union forward, answer in original index order
1042503. Maximum Number of Points From Grid QueriesHardHThe same sweep with a heap instead of a sort for the edges — grid cells enter by value

I⚠︎ Anti-patterns: when the shortest-path machine is wrong#

No new problems. Re-run each of these with the wrong algorithm first, watch it fail, then fix it. That failure is the lesson.

CaseThe wrong machineWhy it failsRight machine
787 (#96)DijkstraFinality invariant is void once a stop budget existsBellman-Ford, k+1 snapshot rounds
1631 (#87)Grid DP4-directional movement admits no evaluation orderDijkstra, or binary search + BFS
1091 (#11)DFSDFS returns a path, never the shortestBFS
Any negative edgeDijkstraA shorter route may be discovered after the node was finalisedBellman-Ford; detect negative cycles with a V-th round
All-pairs at n = 2000Floyd–WarshallO(n³) = 8×10⁹Dijkstra from each source, or reconsider the question
128 (#67)DSUCorrect, but heavier than the O(n) hash-set scanHash set, walking up only from sequence starts
MST for "shortest path between u and v"Kruskal/PrimThe MST path between two nodes is frequently not the shortest pathDijkstra

3.3 Templates#

JavaA — Dijkstra. Lazy deletion; the first pop of a node is final.20 lines
// A — Dijkstra. Lazy deletion; the first pop of a node is final.
long[] dijkstra(int n, List<int[]>[] adj, int src) {   // adj[u] = {v, w}
    long[] dist = new long[n];
    Arrays.fill(dist, Long.MAX_VALUE);
    dist[src] = 0;
    PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[0]));
    pq.add(new long[]{0, src});
    while (!pq.isEmpty()) {
        long[] top = pq.poll();
        int u = (int) top[1];
        if (top[0] > dist[u]) continue;                // stale entry
        for (int[] e : adj[u]) {
            long nd = dist[u] + e[1];                  // ← the relaxation is the parameter
            if (nd < dist[e[0]]) { dist[e[0]] = nd; pq.add(new long[]{nd, e[0]}); }
        }
    }
    return dist;
}
// Bottleneck variant (778, 1631, 1102): nd = Math.max(dist[u], w)
// Probability variant (1514): max-heap, nd = dist[u] * w, keep the larger
JavaD — 0-1 BFS. Deque replaces the heap; zero-cost to the front, one-cost to the back.19 lines
// D — 0-1 BFS. Deque replaces the heap; zero-cost to the front, one-cost to the back.
int[] zeroOneBfs(int n, List<int[]>[] adj, int src) {
    int[] dist = new int[n];
    Arrays.fill(dist, Integer.MAX_VALUE);
    dist[src] = 0;
    Deque<Integer> dq = new ArrayDeque<>();
    dq.addFirst(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.addFirst(v); else dq.addLast(v);
            }
        }
    }
    return dist;
}
JavaE — Bellman-Ford with at most k edges. The snapshot is mandatory.16 lines
// E — Bellman-Ford with at most k edges. The snapshot is mandatory.
int cheapest(int n, int[][] flights, int src, int dst, int k) {
    int[] dist = new int[n];
    Arrays.fill(dist, Integer.MAX_VALUE);
    dist[src] = 0;
    for (int round = 0; round <= k; round++) {
        int[] prev = dist.clone();                     // ← without this, one round
        for (int[] f : flights) {                      //    can chain many edges
            if (prev[f[0]] == Integer.MAX_VALUE) continue;
            dist[f[1]] = Math.min(dist[f[1]], prev[f[0]] + f[2]);
        }
    }
    return dist[dst] == Integer.MAX_VALUE ? -1 : dist[dst];
}
// Drop the k cap and run V-1 rounds for plain Bellman-Ford.
// A V-th round that still improves anything ⇒ a negative cycle.
JavaF — Floyd-Warshall. k MUST be the outer loop.11 lines
// F — Floyd-Warshall. k MUST be the outer loop.
void floyd(int[][] d, int n) {                         // d[i][j] preloaded, INF elsewhere
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++) {
            if (d[i][k] == INF) continue;
            for (int j = 0; j < n; j++) {
                if (d[k][j] == INF) continue;
                d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
            }
        }
}
JavaH — offline queries + DSU sweep.18 lines
// H — offline queries + DSU sweep.
boolean[] answer(int n, int[][] edges, int[][] queries) {   // q = {u, v, limit}
    Arrays.sort(edges, Comparator.comparingInt(e -> e[2]));
    Integer[] order = new Integer[queries.length];
    for (int i = 0; i < order.length; i++) order[i] = i;
    Arrays.sort(order, Comparator.comparingInt(i -> queries[i][2]));

    DSU dsu = new DSU(n);
    boolean[] out = new boolean[queries.length];
    int e = 0;
    for (int qi : order) {                                  // queries in limit order
        while (e < edges.length && edges[e][2] < queries[qi][2]) {
            dsu.union(edges[e][0], edges[e][1]);
            e++;
        }
        out[qi] = dsu.find(queries[qi][0]) == dsu.find(queries[qi][1]);
    }
    return out;                                             // answers in ORIGINAL order
}

3.4 Failure modes#

#FailureSymptomFix
3.1int distance accumulatorOverflow on 1e5 edges of weight 1e4long for distances, always
3.2No stale-entry skip in DijkstraWorks, but the heap grows to O(E) and TLEs on dense inputsif (d > dist[u]) continue; as the first line after the pop
3.3visited[] used instead of comparing distancesA better route arriving later is discardedDijkstra needs no visited array; the nd < dist[v] test is the guard
3.4Dijkstra on a graph with negative weightsWrong answer, no errorBellman-Ford
3.5Bellman-Ford without the per-round snapshotA single round chains multiple edges; the k cap is silently ignoredint[] prev = dist.clone() at the top of each round
3.6Floyd with k in the inner loopWrong answers on paths of length ≥ 3k outermost. There is no exception
3.7INF + w overflow in FloydNegative distances appearSkip when either operand is INF, or use INF = 1e9 with long
3.80-1 BFS with a general weightWrong answer, since the deque no longer holds a monotone frontierOnly weights in {0, 1}; anything else needs Dijkstra
3.9feasible(x) not proved monotone before binary searchingConverges to garbage that passes small testsProve monotonicity on paper first — Bundle 01 §4.1 Step 2
3.10Offline sweep answering in sorted orderAnswers correct but permutedKeep the original query index and write back to out[qi]
3.11Augmented state stored as dist[node]Too-large answer or an infinite loopIndex by the full state tuple