Distances between every pair of nodes
A directed graph has n nodes numbered 0 to n − 1. Each triple
[u, v, w] in edges is an edge from u to v with non-negative
weight w; the same pair may appear more than once. Return an n × n matrix whose entry
[i][j] is the length of the shortest path from i to j, or −1 if
there is none. The diagonal is 0.
Examples
| Input | Output | Why |
|---|---|---|
n = 2, edges = [[0,1,5]] | [[0,5],[-1,0]] | Node 1 cannot reach node 0. |
n = 3, edges = [[0,1,9],[0,1,4],[1,2,0]] | [[0,4,4],[-1,0,0],[-1,-1,0]] | The lighter of two parallel edges counts; zero weights are allowed. |
n = 4, edges = [[0,1,3],[1,2,1],[0,2,7],[2,3,2],[3,0,1],[3,1,6]] | [[0,3,4,6],[4,0,1,3],[3,6,0,2],[1,4,5,0]] | The figure in the lesson. |
Constraints
- 1 ≤ n ≤ 150
- 0 ≤ edges.length ≤ n(n − 1)
- 0 ≤ w ≤ 10⁴
Hints
Hint 1
Start from a matrix holding the direct edge weights (the smallest, if an edge repeats), 0 on the diagonal and infinity elsewhere.
Hint 2
For each node k in turn, allow paths to pass through k: d[i][j] = min(d[i][j], d[i][k] + d[k][j]).
Hint 3
k must be the outermost loop. Convert infinity to −1 only at the end.
Where people lose marks
- Putting the k loop inside the i and j loops.
- Overwriting a parallel edge with a heavier one instead of keeping the minimum.
- Adding two infinities, which overflows in fixed-width integers.
Dry run on 4,[[0,1,3],[1,2,1],[0,2,7],[2,3,2],[3,0,1],[3,1,6]]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 5
Start: 0 on the diagonal, the edge weight where there is an edge, ∞ everywhere else. These are the shortest paths that use no intermediate node.
-
Step 2 / 5
k = 0: allow paths through node 0. Row 0 and column 0 (shaded) are read; 2 entries improve: 3→1 = 4, 3→2 = 8.
-
Step 3 / 5
k = 1: allow paths through node 1. Row 1 and column 1 (shaded) are read; 2 entries improve: 0→2 = 4, 3→2 = 5.
-
Step 4 / 5
k = 2: allow paths through node 2. Row 2 and column 2 (shaded) are read; 2 entries improve: 0→3 = 6, 1→3 = 3.
-
Step 5 / 5
k = 3: allow paths through node 3. Row 3 and column 3 (shaded) are read; 3 entries improve: 1→0 = 4, 2→0 = 3, 2→1 = 6. Every node has been allowed as an intermediate: the matrix holds all shortest distances.
How to think about it
Floyd–Warshall builds shortest paths by widening the set of nodes a path may pass through. After round
k, d[i][j] is the shortest path from i to j whose intermediate nodes are all at most k. Adding node k
either does not help, or the best path goes i → … → k → … → j, and both halves only use intermediates below k,
which are already correct. After the last round every node is allowed.
Keep infinity as a large sentinel while computing and skip any sum that involves it, so that an unreachable pair is never "improved" by an overflowing addition.
Complexity. Time O(n³ + m) · Space O(n²)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def all_pairs(n, edges):
INF = float('inf')
d = [[0 if i == j else INF for j in range(n)] for i in range(n)]
for u, v, w in edges:
d[u][v] = min(d[u][v], w) # keep the lighter of parallel edges
for k in range(n): # allow k as an intermediate node
dk = d[k]
for i in range(n):
dik = d[i][k]
if dik == INF:
continue
di = d[i]
for j in range(n):
if dik + dk[j] < di[j]:
di[j] = dik + dk[j]
return [[-1 if x == INF else x for x in row] for row in d]#include <algorithm>
#include <climits>
#include <vector>
std::vector<std::vector<long long>> solve(int n, const std::vector<std::vector<int>>& edges) {
const long long INF = LLONG_MAX / 4;
std::vector<std::vector<long long>> d(n, std::vector<long long>(n, INF));
for (int i = 0; i < n; ++i) d[i][i] = 0;
for (const auto& e : edges) d[e[0]][e[1]] = std::min<long long>(d[e[0]][e[1]], e[2]);
for (int k = 0; k < n; ++k) // allow k as an intermediate
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 && d[i][k] + d[k][j] < d[i][j]) d[i][j] = d[i][k] + d[k][j];
}
for (auto& row : d)
for (auto& x : row)
if (x == INF) x = -1;
return d;
}class Solution {
public int[][] allPairs(int n, int[][] edges) {
final long INF = Long.MAX_VALUE / 4;
long[][] d = new long[n][n];
for (int i = 0; i < n; i++) {
java.util.Arrays.fill(d[i], INF);
d[i][i] = 0;
}
for (int[] e : edges) d[e[0]][e[1]] = Math.min(d[e[0]][e[1]], e[2]);
for (int k = 0; k < n; k++) // allow k as an intermediate
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 && d[i][k] + d[k][j] < d[i][j]) d[i][j] = d[i][k] + d[k][j];
}
int[][] out = new int[n][n];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) out[i][j] = d[i][j] == INF ? -1 : (int) d[i][j];
return out;
}
}function allPairs(n, edges) {
const d = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 0 : Infinity)));
for (const [u, v, w] of edges) d[u][v] = Math.min(d[u][v], w); // lighter parallel edge
for (let k = 0; k < n; k++) { // allow k as an intermediate
for (let i = 0; i < n; i++) {
if (d[i][k] === Infinity) continue;
for (let j = 0; j < n; j++) {
if (d[i][k] + d[k][j] < d[i][j]) d[i][j] = d[i][k] + d[k][j];
}
}
}
return d.map(row => row.map(x => (x === Infinity ? -1 : x)));
}fn solve(n: i64, edges: &[[i64; 3]]) -> Vec<Vec<i64>> {
let n = n as usize;
let mut d: Vec<Vec<Option<i64>>> = (0..n).map(|i| (0..n).map(|j| if i == j { Some(0) } else { None }).collect()).collect();
for e in edges {
let (u, v, w) = (e[0] as usize, e[1] as usize, e[2]);
d[u][v] = Some(d[u][v].map_or(w, |x| x.min(w))); // lighter parallel edge
}
for k in 0..n { // allow k as an intermediate
for i in 0..n {
let Some(dik) = d[i][k] else { continue };
for j in 0..n {
if let Some(dkj) = d[k][j] {
if d[i][j].map_or(true, |x| dik + dkj < x) {
d[i][j] = Some(dik + dkj);
}
}
}
}
}
d.into_iter().map(|row| row.into_iter().map(|x| x.unwrap_or(-1)).collect()).collect()
}import Data.List (foldl')
import qualified Data.Map.Strict as M
solve :: Int -> [[Int]] -> [[Int]]
solve n edges = [[maybe (-1) id (M.lookup (i, j) final) | j <- [0 .. n - 1]] | i <- [0 .. n - 1]]
where
-- a map holds only the finite distances; a missing key is infinity
start = M.fromListWith min ([((i, i), 0) | i <- [0 .. n - 1]] ++ [((u, v), w) | [u, v, w] <- edges])
final = foldl' through start [0 .. n - 1]
through d k = M.fromList
[ ((i, j), best)
| i <- [0 .. n - 1], j <- [0 .. n - 1]
, let viaK = (+) <$> M.lookup (i, k) d <*> M.lookup (k, j) d
, Just best <- [minMaybe (M.lookup (i, j) d) viaK] ]
minMaybe (Just a) (Just b) = Just (min a b)
minMaybe a Nothing = a
minMaybe Nothing b = b
Variants to try
- Report whether the graph has a negative cycle (some d[i][i] < 0 after the loops).
- Return the actual path for one pair by keeping a next-step table.
- Transitive closure: replace min and + with or and and to get a reachability matrix.
Next problems
The 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 → Easy12 minCount 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 →Write a solution and run it against the real test table.
Keep going
The 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 → Easy12 minCount 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 → GraphsThe pattern behind it
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.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.