Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Graph Algorithms

Builds on Dijkstra, which you haven’t finished yet.

P3MediumMental model~3 min speedrun

Floyd-Warshall (All-Pairs Shortest Path)

The problem

Course Schedule IV (LC 1462)(opens leetcode.com)

You answer a list of yes/no questions about course order. Query [uj, vj] in queries asks whether course uj must be taken before course vj, and the answer is true exactly when a chain of pairs from prerequisites leads from uj to vj: [uj, x1], [x1, x2], ..., [xm, vj], where the single pair [uj, vj] is the shortest possible chain.

In prerequisites, a pair [ai, bi] means course ai comes before course bi. The courses are numbered 0 to numCourses - 1, and the pairs never form a cycle. Return the answers as a list of booleans, one per query, in the order of queries.

Example 1
Input:numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
Output:[false,true]
Course 1 is taken first, so the query [1, 0] is true; no chain leads from course 0 to course 1, so [0, 1] is false.
10

Floyd-Warshall

distance table ➔ allow one more middle node per round ➔ all shortest distances
Spot it when: You need the shortest distance, or just reachability, between every pair of nodes (or answers to many pair queries), and the graph is small: a few hundred nodes at most.
Floyd-Warshall
Python
1from typing import List
2 
3def floyd_warshall(n: int, edges: List[List[int]]) -> List[List[float]]:
4 """Floyd-Warshall (the core of Course Schedule IV): shortest distance between every
5 pair of nodes.
6 Time: O(V^3) | Space: O(V^2)
7 """
8 dist = [[float("inf")] * n for _ in range(n)]
9 for i in range(n):
10 dist[i][i] = 0 # a node reaches itself at distance 0
11 for u, v, w in edges:
12 # directed edge u -> v; keep the lighter of two parallel edges
13 dist[u][v] = min(dist[u][v], w)
14 for k in range(n): # k OUTERMOST: allow node k as a stop
15 for i in range(n):
16 for j in range(n):
17 if dist[i][k] + dist[k][j] < dist[i][j]:
18 dist[i][j] = dist[i][k] + dist[k][j] # go through k
19 return dist
Invariant: Once the outer loop has finished with k, dist[i][j] is the length of the shortest path from i to j whose stops all lie among nodes 0..k.

Allow the nodes as stops one at a time: for each k, let every pair i, j go through k when dist[i][k] + dist[k][j] < dist[i][j]. This is correct because, once k is allowed, the shortest i -> j path with stops in 0..k either avoids k, and is already in dist[i][j], or passes through k once and splits into an i -> k part and a k -> j part whose stops lie in 0..k - 1, and both of those are already in the table. The trap is the loop order: for k in range(n) must be the outermost loop. With k inside the i and j loops, dist[i][k] is read before the paths through the smaller stops have been allowed, and some pairs keep a distance that is too long, or no path at all.

See it run

Floyd-Warshall on 4 nodes, one k round at a time

k - · dist[0] [0, 3, inf, inf]

k 0 · dist[0] [0, 3, inf, inf]

k 1 · dist[0] [0, 3, inf, inf]

k 1 · dist[0] [0, 3, 4, inf]

k 1 · dist[0] [0, 3, 4, 7]

k 1 · dist[0] [0, 3, 4, 7]

k 1 · dist[0] [0, 3, 4, 7]

k 2 · dist[0] [0, 3, 4, 7]

k 2 · dist[0] [0, 3, 4, 5]

k 2 · dist[0] [0, 3, 4, 5]

k 2 · dist[0] [0, 3, 4, 5]

k 2 · dist[0] [0, 3, 4, 5]

k 3 · dist[0] [0, 3, 4, 5]

k 3 · dist[0] [0, 3, 4, 5] · answer 3

Row i and column j are nodes. The cell in row i and column j is dist[i][j], the shortest known distance from node i to node j.

  • row/col k the paths into and out of the middle node k
  • i to k dist[i][k], the first part of the path
  • k to j dist[k][j], the second part of the path
  • new dist[i][j], which gets a shorter value
  • updated a cell already shortened in this round

dist[i][j] starts as the cost of the edge i to j, 0 on the diagonal, and inf where no edge exists. No middle node is allowed yet.

k = 0: allow node 0 as a middle stop. No pair gets shorter.

k = 1: allow node 1 as a middle stop. 4 pairs get shorter.

dist[0][1] + dist[1][2] = 3 + 1 = 4 is less than the old dist[0][2]. So dist[0][2] = 4.

dist[0][1] + dist[1][3] = 3 + 4 = 7 is less than the old dist[0][3]. So dist[0][3] = 7.

dist[2][1] + dist[1][0] = 1 + 3 = 4 is less than the old dist[2][0]. So dist[2][0] = 4.

dist[3][1] + dist[1][0] = 4 + 3 = 7 is less than the old dist[3][0]. So dist[3][0] = 7.

k = 2: allow node 2 as a middle stop. 4 pairs get shorter.

dist[0][2] + dist[2][3] = 4 + 1 = 5 is less than the old dist[0][3]. So dist[0][3] = 5.

dist[1][2] + dist[2][3] = 1 + 1 = 2 is less than the old dist[1][3]. So dist[1][3] = 2.

dist[3][2] + dist[2][0] = 1 + 4 = 5 is less than the old dist[3][0]. So dist[3][0] = 5.

dist[3][2] + dist[2][1] = 1 + 1 = 2 is less than the old dist[3][1]. So dist[3][1] = 2.

k = 3: allow node 3 as a middle stop. No pair gets shorter.

Count the cities within distance 4 of each city: [2, 3, 3, 2]. City 3 has the fewest, 2. The answer is 3.

i = 0
j = 00
j = 13
j = 2inf
j = 3inf
i = 1
3
0
1
4
i = 2
inf
1
0
1
i = 3
inf
4
1
0
1 / 14
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Putting for k in range(n) inside the i and j loops

    Mistake
    for i in range(n):
        for j in range(n):
            for k in range(n):
    Fix
    for k in range(n):
        for i in range(n):
            for j in range(n):

    What happens: With k innermost, the pair (i, j) is finished before the other pairs are updated. dist[i][k] may still miss a path through a smaller stop. So some pairs keep a distance that is too long, or no path at all.

    Example: n = 4, edges = [[0, 3, 1], [3, 2, 1], [2, 1, 1]], read dist[0][1]
    Mistake: returns infFix: returns 3
  2. 2

    Gotcha 2: Using INT_MAX as "no path" in Java or C++

    What happens: In Python, float("inf") + 1 is still inf, so the blueprint is safe. In Java or C++, INT_MAX + 1 overflows to a negative number. dist[i][k] + dist[k][j] then looks shorter than any real path. Use a value such as INT_MAX / 2, or skip pairs where either part is unreached.

  3. 3

    Gotcha 3: Storing edge [u, v] as dist[v][u]

    Mistake
    dist[v][u] = min(dist[v][u], w)
    Fix
    dist[u][v] = min(dist[u][v], w)

    What happens: In Course Schedule IV (LC 1462), the pair [ai, bi] means course ai comes before course bi. So the edge goes from u to v, and dist[u][v] gets its weight. Storing it as dist[v][u] reverses every edge. Every query is then answered backwards.

    Example: n = 2, edges = [[1, 0, 1]], read dist[1][0]
    Mistake: returns infFix: returns 1
  4. 4

    Gotcha 4: Writing dist[u][v] = w for parallel edges

    Mistake
    dist[u][v] = w
    Fix
    dist[u][v] = min(dist[u][v], w)

    What happens: Two edges can join the same pair of nodes. dist[u][v] = w keeps whichever edge comes last. If the lighter edge came first, it is lost.

    Example: n = 2, edges = [[0, 1, 2], [0, 1, 5]], read dist[0][1]
    Mistake: returns 5Fix: returns 2
Not this one if…
  • Only the distances from one source are needed, as in Network Delay Time (LC 743): a heap from that source costs O((V + E) log V), far below V^3. See Dijkstra
  • The path may use at most k edges, as in Cheapest Flights Within K Stops (LC 787): the distance table has no edge count, so the cap needs rounds of relaxation instead. See Bellman-Ford
  • You need one order in which to take every course, as in Course Schedule II (LC 210), not an answer about every pair of courses. See Topological Sort

Deep Dive

Why one more middle node is enough

dist[i][j] is a table of shortest known distances. The outer loop allows one more node k as a middle stop in each round. The card's invariant says: after the round for k, dist[i][j] is the shortest path from i to j that uses only the nodes 0..k as stops.

Take the shortest path from i to j with stops in 0..k. Either it does not pass through k, or it does. If it does not, it is already in dist[i][j] from the earlier rounds.

If it passes through k, it passes once, because a shortest path does not repeat a node. Cut it at k. The part from i to k and the part from k to j both use only the stops 0..k - 1, so both are already in the table. The test dist[i][k] + dist[k][j] < dist[i][j] finds exactly this case.

Example: in the board below, the direct edge from node 0 to node 2 does not exist. In the round for k = 1, dist[0][1] + dist[1][2] = 3 + 1 = 4 gives dist[0][2] = 4.

One trace: one k round at a time

The board runs floyd_warshall on the first example of Find the City With the Smallest Number of Neighbors at a Threshold Distance (LC 1334): 4 cities and the roads 0-1:3, 1-2:1, 1-3:4 and 2-3:1. Each road works in both directions, so the board lists each road twice. The threshold is 4.

Round k = 0 changes nothing, because node 0 has only one neighbour. Round k = 1 joins node 0 to nodes 2 and 3 through node 1, so dist[0][2] = 4 and dist[0][3] = 7.

Round k = 2 reads those new values. It sets dist[0][3] = 4 + 1 = 5, and dist[1][3] = 1 + 1 = 2. Round k = 3 changes nothing.

The final table has row 0 as [0, 3, 4, 5]. The number of cities within distance 4 is [2, 3, 3, 2]. Cities 0 and 3 tie at 2, and the problem asks for the larger number, so the answer is 3.

The trap: the loop order

for k in range(n) must be the outermost loop. Round k reads values that the rounds for smaller stops must have finished.

With k innermost, the pair (i, j) is finished before the other pairs have been updated. Example: n = 4 and edges 0 -> 3, 3 -> 2, 2 -> 1, each of cost 1. The right value of dist[0][1] is 3. With i, j, k order, the result is infinity, because dist[0][1] is finished before dist[0][2] has a value.

Two smaller traps. Store a directed edge [u, v] in dist[u][v], not in dist[v][u]. And use min for parallel edges, so a lighter edge that comes first is not lost.

In Java or C++, do not use INT_MAX for no path. INT_MAX + 1 overflows to a negative number. Use INT_MAX / 2, or skip the pair.

The cost, and when to pick something else

Three nested loops over n nodes run n * n * n checks, so the time is O(V^3) and the table takes O(V^2) space. For 4 nodes that is 64 checks. For 100 nodes it is 1000000.

That is fine for a few hundred nodes at most. For a large sparse graph, running Dijkstra from every node (the cheapest-first card) costs about V times O((V + E) log V), which is much less.

If you need distances from only one source, as in Network Delay Time (LC 743), use Dijkstra. If the path may use at most k edges, as in Cheapest Flights Within K Stops (LC 787), use the rounds of Bellman-Ford: this table has no edge count.

If the question is only whether one node can reach another, as in Course Schedule IV (LC 1462), the same loops work with true and false in place of distances. If you need one order in which to take every course, as in Course Schedule II (LC 210), use Topological Sort. That answer is not about pairs.

60-Second Pitch

"I need the shortest distance between every pair of nodes on a small graph, so I use Floyd-Warshall. I keep a table dist, start it from the edges, and allow the nodes as middle stops one at a time. For each k, every pair i, j takes the path through k when dist[i][k] + dist[k][j] is shorter. It is correct because a shortest path with stops in 0..k either avoids k or passes through it once. Then both halves use only the earlier stops, and they are already in the table. That is why k must be the outermost loop. The time is O(V^3) and the space is O(V^2). For a large sparse graph, I would run Dijkstra from each node instead."

Common Questions

Why must k be the outer loop?

Round k assumes the table already holds every shortest path with stops in 0..k - 1. With k inside the other loops, a pair is finished before the other pairs it depends on. Some distances stay too long or infinity.

What are the time and space complexity?

O(V^3) time, because there are three loops over n nodes. O(V^2) space for the table. It suits a graph of a few hundred nodes at most.

How is it different from running Dijkstra from every node?

Dijkstra from every node costs about V times O((V + E) log V). That is less on a sparse graph, but it needs non-negative weights. Floyd-Warshall is shorter to write and allows negative weights, with no negative cycle.

How do you tell that a graph has a negative cycle?

After the loops, check the diagonal. If dist[i][i] is below 0 for some i, a cycle through i has a negative total. Then the distances are not meaningful.

How do you use it only for reachability?

Replace the distances with true and false, and the test with reach[i][k] and reach[k][j]. Set reach[i][j] to true when it holds. This is the idea behind Course Schedule IV (LC 1462).

SWE Reasoning

Spot it

  • The question needs the shortest distance, or only reachability, between every pair of nodes.
  • Or there are many queries about pairs of nodes, and the graph is small.
  • The number of nodes is a few hundred at most, so V cubed is affordable.
  • A threshold or a count per node is asked, such as the cities within a given distance.
  • Weights can be negative, as long as no cycle has a negative total.

The invariant, step by step

  1. dist[i][i] = 0, dist[u][v] is the lightest edge from u to v, and all other entries are infinity. No middle node is allowed yet.
  2. The outer loop picks k. From now on, node k may be a middle stop.
  3. For every pair i, j, test whether dist[i][k] + dist[k][j] is less than dist[i][j]. If it is, take the shorter path.
  4. A shortest path with stops in 0..k either avoids k, so it is already in dist[i][j], or it passes through k once and splits into two parts that use only 0..k - 1.
  5. Both parts are already in the table, so the test finds the best path. After the round for k, dist[i][j] is the shortest path with stops in 0..k.
  6. After the last k, every node was allowed, and dist holds all shortest distances.

Traps

  • k not outermost. Pairs are finished too early. Some distances are too long or infinity.
  • INT_MAX for no path in Java or C++. The sum overflows. Use half of the maximum, or skip unreached pairs.
  • Storing the edge backwards. A directed edge [u, v] goes in dist[u][v].
  • Plain assignment for parallel edges. Use min, so the lighter edge stays.
  • A large graph. O(V^3) is too slow beyond a few hundred nodes. Run Dijkstra from each node instead.

Practice

Canonical LeetCode:Find the City With the Smallest Number of Neighbors at a Threshold Distance (LC 1334)Course Schedule IV (LC 1462)Minimum Cost to Convert String I (LC 2976)
Theory Context•Graph Algorithms

Builds on Dijkstra, which you haven’t finished yet.

P3MediumMental model~3 min speedrun

Floyd-Warshall (All-Pairs Shortest Path)

The problem

Course Schedule IV (LC 1462)(opens leetcode.com)

You answer a list of yes/no questions about course order. Query [uj, vj] in queries asks whether course uj must be taken before course vj, and the answer is true exactly when a chain of pairs from prerequisites leads from uj to vj: [uj, x1], [x1, x2], ..., [xm, vj], where the single pair [uj, vj] is the shortest possible chain.

In prerequisites, a pair [ai, bi] means course ai comes before course bi. The courses are numbered 0 to numCourses - 1, and the pairs never form a cycle. Return the answers as a list of booleans, one per query, in the order of queries.

Example 1
Input:numCourses = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
Output:[false,true]
Course 1 is taken first, so the query [1, 0] is true; no chain leads from course 0 to course 1, so [0, 1] is false.
10

Floyd-Warshall

distance table ➔ allow one more middle node per round ➔ all shortest distances
Spot it when: You need the shortest distance, or just reachability, between every pair of nodes (or answers to many pair queries), and the graph is small: a few hundred nodes at most.
Floyd-Warshall
Python
1from typing import List
2 
3def floyd_warshall(n: int, edges: List[List[int]]) -> List[List[float]]:
4 """Floyd-Warshall (the core of Course Schedule IV): shortest distance between every
5 pair of nodes.
6 Time: O(V^3) | Space: O(V^2)
7 """
8 dist = [[float("inf")] * n for _ in range(n)]
9 for i in range(n):
10 dist[i][i] = 0 # a node reaches itself at distance 0
11 for u, v, w in edges:
12 # directed edge u -> v; keep the lighter of two parallel edges
13 dist[u][v] = min(dist[u][v], w)
14 for k in range(n): # k OUTERMOST: allow node k as a stop
15 for i in range(n):
16 for j in range(n):
17 if dist[i][k] + dist[k][j] < dist[i][j]:
18 dist[i][j] = dist[i][k] + dist[k][j] # go through k
19 return dist
Invariant: Once the outer loop has finished with k, dist[i][j] is the length of the shortest path from i to j whose stops all lie among nodes 0..k.

Allow the nodes as stops one at a time: for each k, let every pair i, j go through k when dist[i][k] + dist[k][j] < dist[i][j]. This is correct because, once k is allowed, the shortest i -> j path with stops in 0..k either avoids k, and is already in dist[i][j], or passes through k once and splits into an i -> k part and a k -> j part whose stops lie in 0..k - 1, and both of those are already in the table. The trap is the loop order: for k in range(n) must be the outermost loop. With k inside the i and j loops, dist[i][k] is read before the paths through the smaller stops have been allowed, and some pairs keep a distance that is too long, or no path at all.

See it run

Floyd-Warshall on 4 nodes, one k round at a time

k - · dist[0] [0, 3, inf, inf]

k 0 · dist[0] [0, 3, inf, inf]

k 1 · dist[0] [0, 3, inf, inf]

k 1 · dist[0] [0, 3, 4, inf]

k 1 · dist[0] [0, 3, 4, 7]

k 1 · dist[0] [0, 3, 4, 7]

k 1 · dist[0] [0, 3, 4, 7]

k 2 · dist[0] [0, 3, 4, 7]

k 2 · dist[0] [0, 3, 4, 5]

k 2 · dist[0] [0, 3, 4, 5]

k 2 · dist[0] [0, 3, 4, 5]

k 2 · dist[0] [0, 3, 4, 5]

k 3 · dist[0] [0, 3, 4, 5]

k 3 · dist[0] [0, 3, 4, 5] · answer 3

Row i and column j are nodes. The cell in row i and column j is dist[i][j], the shortest known distance from node i to node j.

  • row/col k the paths into and out of the middle node k
  • i to k dist[i][k], the first part of the path
  • k to j dist[k][j], the second part of the path
  • new dist[i][j], which gets a shorter value
  • updated a cell already shortened in this round

dist[i][j] starts as the cost of the edge i to j, 0 on the diagonal, and inf where no edge exists. No middle node is allowed yet.

k = 0: allow node 0 as a middle stop. No pair gets shorter.

k = 1: allow node 1 as a middle stop. 4 pairs get shorter.

dist[0][1] + dist[1][2] = 3 + 1 = 4 is less than the old dist[0][2]. So dist[0][2] = 4.

dist[0][1] + dist[1][3] = 3 + 4 = 7 is less than the old dist[0][3]. So dist[0][3] = 7.

dist[2][1] + dist[1][0] = 1 + 3 = 4 is less than the old dist[2][0]. So dist[2][0] = 4.

dist[3][1] + dist[1][0] = 4 + 3 = 7 is less than the old dist[3][0]. So dist[3][0] = 7.

k = 2: allow node 2 as a middle stop. 4 pairs get shorter.

dist[0][2] + dist[2][3] = 4 + 1 = 5 is less than the old dist[0][3]. So dist[0][3] = 5.

dist[1][2] + dist[2][3] = 1 + 1 = 2 is less than the old dist[1][3]. So dist[1][3] = 2.

dist[3][2] + dist[2][0] = 1 + 4 = 5 is less than the old dist[3][0]. So dist[3][0] = 5.

dist[3][2] + dist[2][1] = 1 + 1 = 2 is less than the old dist[3][1]. So dist[3][1] = 2.

k = 3: allow node 3 as a middle stop. No pair gets shorter.

Count the cities within distance 4 of each city: [2, 3, 3, 2]. City 3 has the fewest, 2. The answer is 3.

i = 0
j = 00
j = 13
j = 2inf
j = 3inf
i = 1
3
0
1
4
i = 2
inf
1
0
1
i = 3
inf
4
1
0
1 / 14
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Putting for k in range(n) inside the i and j loops

    Mistake
    for i in range(n):
        for j in range(n):
            for k in range(n):
    Fix
    for k in range(n):
        for i in range(n):
            for j in range(n):

    What happens: With k innermost, the pair (i, j) is finished before the other pairs are updated. dist[i][k] may still miss a path through a smaller stop. So some pairs keep a distance that is too long, or no path at all.

    Example: n = 4, edges = [[0, 3, 1], [3, 2, 1], [2, 1, 1]], read dist[0][1]
    Mistake: returns infFix: returns 3
  2. 2

    Gotcha 2: Using INT_MAX as "no path" in Java or C++

    What happens: In Python, float("inf") + 1 is still inf, so the blueprint is safe. In Java or C++, INT_MAX + 1 overflows to a negative number. dist[i][k] + dist[k][j] then looks shorter than any real path. Use a value such as INT_MAX / 2, or skip pairs where either part is unreached.

  3. 3

    Gotcha 3: Storing edge [u, v] as dist[v][u]

    Mistake
    dist[v][u] = min(dist[v][u], w)
    Fix
    dist[u][v] = min(dist[u][v], w)

    What happens: In Course Schedule IV (LC 1462), the pair [ai, bi] means course ai comes before course bi. So the edge goes from u to v, and dist[u][v] gets its weight. Storing it as dist[v][u] reverses every edge. Every query is then answered backwards.

    Example: n = 2, edges = [[1, 0, 1]], read dist[1][0]
    Mistake: returns infFix: returns 1
  4. 4

    Gotcha 4: Writing dist[u][v] = w for parallel edges

    Mistake
    dist[u][v] = w
    Fix
    dist[u][v] = min(dist[u][v], w)

    What happens: Two edges can join the same pair of nodes. dist[u][v] = w keeps whichever edge comes last. If the lighter edge came first, it is lost.

    Example: n = 2, edges = [[0, 1, 2], [0, 1, 5]], read dist[0][1]
    Mistake: returns 5Fix: returns 2
Not this one if…
  • Only the distances from one source are needed, as in Network Delay Time (LC 743): a heap from that source costs O((V + E) log V), far below V^3. See Dijkstra
  • The path may use at most k edges, as in Cheapest Flights Within K Stops (LC 787): the distance table has no edge count, so the cap needs rounds of relaxation instead. See Bellman-Ford
  • You need one order in which to take every course, as in Course Schedule II (LC 210), not an answer about every pair of courses. See Topological Sort

Deep Dive

Why one more middle node is enough

dist[i][j] is a table of shortest known distances. The outer loop allows one more node k as a middle stop in each round. The card's invariant says: after the round for k, dist[i][j] is the shortest path from i to j that uses only the nodes 0..k as stops.

Take the shortest path from i to j with stops in 0..k. Either it does not pass through k, or it does. If it does not, it is already in dist[i][j] from the earlier rounds.

If it passes through k, it passes once, because a shortest path does not repeat a node. Cut it at k. The part from i to k and the part from k to j both use only the stops 0..k - 1, so both are already in the table. The test dist[i][k] + dist[k][j] < dist[i][j] finds exactly this case.

Example: in the board below, the direct edge from node 0 to node 2 does not exist. In the round for k = 1, dist[0][1] + dist[1][2] = 3 + 1 = 4 gives dist[0][2] = 4.

One trace: one k round at a time

The board runs floyd_warshall on the first example of Find the City With the Smallest Number of Neighbors at a Threshold Distance (LC 1334): 4 cities and the roads 0-1:3, 1-2:1, 1-3:4 and 2-3:1. Each road works in both directions, so the board lists each road twice. The threshold is 4.

Round k = 0 changes nothing, because node 0 has only one neighbour. Round k = 1 joins node 0 to nodes 2 and 3 through node 1, so dist[0][2] = 4 and dist[0][3] = 7.

Round k = 2 reads those new values. It sets dist[0][3] = 4 + 1 = 5, and dist[1][3] = 1 + 1 = 2. Round k = 3 changes nothing.

The final table has row 0 as [0, 3, 4, 5]. The number of cities within distance 4 is [2, 3, 3, 2]. Cities 0 and 3 tie at 2, and the problem asks for the larger number, so the answer is 3.

The trap: the loop order

for k in range(n) must be the outermost loop. Round k reads values that the rounds for smaller stops must have finished.

With k innermost, the pair (i, j) is finished before the other pairs have been updated. Example: n = 4 and edges 0 -> 3, 3 -> 2, 2 -> 1, each of cost 1. The right value of dist[0][1] is 3. With i, j, k order, the result is infinity, because dist[0][1] is finished before dist[0][2] has a value.

Two smaller traps. Store a directed edge [u, v] in dist[u][v], not in dist[v][u]. And use min for parallel edges, so a lighter edge that comes first is not lost.

In Java or C++, do not use INT_MAX for no path. INT_MAX + 1 overflows to a negative number. Use INT_MAX / 2, or skip the pair.

The cost, and when to pick something else

Three nested loops over n nodes run n * n * n checks, so the time is O(V^3) and the table takes O(V^2) space. For 4 nodes that is 64 checks. For 100 nodes it is 1000000.

That is fine for a few hundred nodes at most. For a large sparse graph, running Dijkstra from every node (the cheapest-first card) costs about V times O((V + E) log V), which is much less.

If you need distances from only one source, as in Network Delay Time (LC 743), use Dijkstra. If the path may use at most k edges, as in Cheapest Flights Within K Stops (LC 787), use the rounds of Bellman-Ford: this table has no edge count.

If the question is only whether one node can reach another, as in Course Schedule IV (LC 1462), the same loops work with true and false in place of distances. If you need one order in which to take every course, as in Course Schedule II (LC 210), use Topological Sort. That answer is not about pairs.

60-Second Pitch

"I need the shortest distance between every pair of nodes on a small graph, so I use Floyd-Warshall. I keep a table dist, start it from the edges, and allow the nodes as middle stops one at a time. For each k, every pair i, j takes the path through k when dist[i][k] + dist[k][j] is shorter. It is correct because a shortest path with stops in 0..k either avoids k or passes through it once. Then both halves use only the earlier stops, and they are already in the table. That is why k must be the outermost loop. The time is O(V^3) and the space is O(V^2). For a large sparse graph, I would run Dijkstra from each node instead."

Common Questions

Why must k be the outer loop?

Round k assumes the table already holds every shortest path with stops in 0..k - 1. With k inside the other loops, a pair is finished before the other pairs it depends on. Some distances stay too long or infinity.

What are the time and space complexity?

O(V^3) time, because there are three loops over n nodes. O(V^2) space for the table. It suits a graph of a few hundred nodes at most.

How is it different from running Dijkstra from every node?

Dijkstra from every node costs about V times O((V + E) log V). That is less on a sparse graph, but it needs non-negative weights. Floyd-Warshall is shorter to write and allows negative weights, with no negative cycle.

How do you tell that a graph has a negative cycle?

After the loops, check the diagonal. If dist[i][i] is below 0 for some i, a cycle through i has a negative total. Then the distances are not meaningful.

How do you use it only for reachability?

Replace the distances with true and false, and the test with reach[i][k] and reach[k][j]. Set reach[i][j] to true when it holds. This is the idea behind Course Schedule IV (LC 1462).

SWE Reasoning

Spot it

  • The question needs the shortest distance, or only reachability, between every pair of nodes.
  • Or there are many queries about pairs of nodes, and the graph is small.
  • The number of nodes is a few hundred at most, so V cubed is affordable.
  • A threshold or a count per node is asked, such as the cities within a given distance.
  • Weights can be negative, as long as no cycle has a negative total.

The invariant, step by step

  1. dist[i][i] = 0, dist[u][v] is the lightest edge from u to v, and all other entries are infinity. No middle node is allowed yet.
  2. The outer loop picks k. From now on, node k may be a middle stop.
  3. For every pair i, j, test whether dist[i][k] + dist[k][j] is less than dist[i][j]. If it is, take the shorter path.
  4. A shortest path with stops in 0..k either avoids k, so it is already in dist[i][j], or it passes through k once and splits into two parts that use only 0..k - 1.
  5. Both parts are already in the table, so the test finds the best path. After the round for k, dist[i][j] is the shortest path with stops in 0..k.
  6. After the last k, every node was allowed, and dist holds all shortest distances.

Traps

  • k not outermost. Pairs are finished too early. Some distances are too long or infinity.
  • INT_MAX for no path in Java or C++. The sum overflows. Use half of the maximum, or skip unreached pairs.
  • Storing the edge backwards. A directed edge [u, v] goes in dist[u][v].
  • Plain assignment for parallel edges. Use min, so the lighter edge stays.
  • A large graph. O(V^3) is too slow beyond a few hundred nodes. Run Dijkstra from each node instead.

Practice

Canonical LeetCode:Find the City With the Smallest Number of Neighbors at a Threshold Distance (LC 1334)Course Schedule IV (LC 1462)Minimum Cost to Convert String I (LC 2976)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•