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.

P3MediumBuilt on Dijkstra~4 min speedrun

Bellman-Ford (K-Stop Relaxation)

Built on
DijkstraEdges are relaxed in rounds instead of popping the cheapest node, so a path is limited to a number of edges and negative weights are allowed.
The problem

Cheapest Flights Within K Stops (LC 787)(opens leetcode.com)

Picture an airline network over n cities, numbered 0 to n - 1. Each entry [from_i, to_i, price_i] of flights is a one-way flight: it leaves from_i, lands in to_i, and its ticket costs price_i.

You want to travel from city src to city dst and pay as little as possible in total, but you accept at most k stops: a stop is a city you land in on the way, other than src and dst, so the trip uses at most k + 1 flights. Return the lowest total ticket price of such a trip, or -1 when no trip with at most k stops gets you to dst.

Example 1
Input:n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1
Output:700
Flying 0 -> 1 -> 3 lands in one city on the way (1 stop) and costs 100 + 600 = 700. Going 0 -> 1 -> 2 -> 3 would cost only 400, but it lands in two cities on the way, one more than k = 1 allows.
1001001006002000123

Bellman-Ford

edges ➔ k + 1 rounds of relaxation on a copy ➔ cheapest price within k stops
Spot it when: You need the cheapest path when the number of edges is capped (at most k stops, flights or moves), or when some edge weights are negative, so Dijkstra's settled-means-final rule no longer holds.
Bellman-Ford
Python
1from typing import List
2 
3def bellman_ford_k_stops(n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
4 """Bellman-Ford (Cheapest Flights Within K Stops): lowest total fare of a trip src
5 -> dst that lands in no more than k cities on the way, or -1.
6 Time: O(K · (V + E)) | Space: O(V)
7 """
8 prices = [float("inf")] * n
9 prices[src] = 0 # 0 flights: only src is reached
10 # k stops = at most k + 1 flights, one more per round
11 for _ in range(k + 1):
12 tmp_prices = prices.copy() # write here, read last round's prices
13 for u, v, price in flights:
14 if prices[u] == float("inf"):
15 # no route from the earlier rounds reaches u
16 continue
17 # one more flight, from last round's price
18 if prices[u] + price < tmp_prices[v]:
19 tmp_prices[v] = prices[u] + price
20 prices = tmp_prices # the round is done: publish it
21 return prices[dst] if prices[dst] != float("inf") else -1
Invariant: After round i, prices[v] is the cheapest price of a route from src to v that uses at most i flights, and a round only ever reads prices and writes tmp_prices.

A route with at most k stops has at most k + 1 flights, so run exactly k + 1 rounds and let each round add one more flight: for every flight u -> v, keep prices[u] + price in tmp_prices[v] when it is cheaper. This is correct because every route of at most i flights is a route of at most i - 1 flights, whose cheapest price is already in prices, plus one last flight, and round i tries every possible last flight; so after round i, prices[v] is the cheapest route to v of at most i flights. The trap is relaxing in place: a round must read the previous round's prices and write into its copy, tmp_prices = prices.copy(). Written straight into prices, a flight relaxed earlier in the round feeds the next flight in the same round, one round chains several flights, and a cheaper route with too many stops wins.

See it run

Bellman-Ford with at most 1 stops, from city 0 to city 3, step by step

round 0 of 2 · prices [0, inf, inf, inf] · tmp_prices -

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, inf, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, inf, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, inf, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, inf, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, 700]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, 700]

round 2 of 2 · prices [0, 100, 200, 700] · tmp_prices [0, 100, 200, 700]

round 2 of 2 · prices [0, 100, 200, 700] · tmp_prices [0, 100, 200, 700] · answer 700

Column j is city j. The top row is prices. The bottom row is tmp_prices, the copy that one round writes to.

  • read prices[u], the price from the last round
  • target tmp_prices[v], which the flight tries to lower
  • written a value just set
  • answer prices[dst], the final answer

prices[0] = 0 and every other city is inf. 1 stop allows at most 2 flights, so run 2 rounds.

Round 1. tmp_prices = prices.copy(). The round reads prices and writes only tmp_prices.

Flight 0 to 1 costs 100. prices[0] + 100 = 100 is less than tmp_prices[1] = inf. So tmp_prices[1] = 100.

Flight 1 to 2 costs 100. prices[1] is inf, so skip it. Reading tmp_prices[1] = 100 would give 200, but that route uses more than 1 flight.

Flight 2 to 0 costs 100. prices[2] is inf, so no route from the earlier rounds reaches 2. Skip it.

Flight 1 to 3 costs 600. prices[1] is inf, so skip it. Reading tmp_prices[1] = 100 would give 700, but that route uses more than 1 flight.

Flight 2 to 3 costs 200. prices[2] is inf, so no route from the earlier rounds reaches 2. Skip it.

Round 1 is done. prices = tmp_prices. Now prices[v] is the cheapest route to v with at most 1 flight.

Round 2. tmp_prices = prices.copy(). The round reads prices and writes only tmp_prices.

Flight 0 to 1 costs 100. prices[0] + 100 = 100 is not less than tmp_prices[1] = 100. Keep it.

Flight 1 to 2 costs 100. prices[1] + 100 = 200 is less than tmp_prices[2] = inf. So tmp_prices[2] = 200.

Flight 2 to 0 costs 100. prices[2] is inf, so no route from the earlier rounds reaches 2. Skip it.

Flight 1 to 3 costs 600. prices[1] + 600 = 700 is less than tmp_prices[3] = inf. So tmp_prices[3] = 700.

Flight 2 to 3 costs 200. prices[2] is inf, so skip it. Reading tmp_prices[2] = 200 would give 400, but that route uses more than 2 flights.

Round 2 is done. prices = tmp_prices. Now prices[v] is the cheapest route to v with at most 2 flights.

All 2 rounds are done. prices[3] is 700, so the answer is 700.

prices
city 00written
city 1inf
city 2inf
city 3inf
tmp_prices
1 / 16
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing into prices in place instead of a copy

    Mistake
    tmp_prices = prices
    Fix
    tmp_prices = prices.copy()

    What happens: tmp_prices = prices makes a second name for the same list. A price written in a round is then read again in the same round. So one round can add several flights. A cheap route with too many stops wins. With no stop limit, relaxing in place is still correct, and only finishes sooner.

    Example: Cheapest Flights Within K Stops (LC 787): n = 4, flights = [[0, 1, 100], [1, 2, 100], [2, 0, 100], [1, 3, 600], [2, 3, 200]], src = 0, dst = 3, k = 1
    Mistake: returns 400Fix: returns 700
  2. 2

    Gotcha 2: Running range(k) rounds instead of range(k + 1)

    Mistake
    for _ in range(k):
    Fix
    for _ in range(k + 1):

    What happens: k stops means up to k + 1 flights. Each round adds exactly one flight. With range(k), the last flight is never added. Routes that use all k stops are missed.

    Example: n = 4, flights = [[0, 1, 100], [1, 2, 100], [2, 0, 100], [1, 3, 600], [2, 3, 200]], src = 0, dst = 3, k = 1
    Mistake: returns -1Fix: returns 700
  3. 3

    Gotcha 3: Returning prices[dst] when it is still float("inf")

    Mistake
    return prices[dst]
    Fix
    return prices[dst] if prices[dst] != float("inf") else -1

    What happens: prices[dst] stays float("inf") when no route with at most k stops reaches dst. The problem asks for -1 then.

    Example: n = 3, flights = [[0, 1, 100]], src = 0, dst = 2, k = 1
    Mistake: returns infFix: returns -1
  4. 4

    Gotcha 4: Dropping the prices[u] == INT_MAX check in Java or C++

    Mistake
    if (prices[u] + price < tmp[v]) {
    Fix
    if (prices[u] == INT_MAX) continue;
    if (prices[u] + price < tmp[v]) {

    What happens: In Python, float("inf") + price is still inf, so the check changes nothing. That is why this mistake has no example. In Java or C++, an unreached city often holds INT_MAX. INT_MAX + price overflows to a negative number. That number looks like the cheapest price of all.

Not this one if…
  • Nothing caps the number of edges and every weight is non-negative, as in Network Delay Time (LC 743): switch to Dijkstra. See Dijkstra
  • You need the shortest distance between every pair of cities, as in Find the City With the Smallest Number of Neighbors at a Threshold Distance (LC 1334), not the cheapest route from one source. See Floyd-Warshall

Deep Dive

What changes from Dijkstra: rounds on a copy, not a heap

The Dijkstra card keeps a heap and settles one node at a time, by cost. This card has no heap. It runs rounds, and round i adds one more flight to every route.

Why this is needed: Dijkstra settles a node by its cost alone, and it cannot count flights. In Cheapest Flights Within K Stops (LC 787), the cheapest route overall may use too many stops. For the example in the board below, Dijkstra would find the route 0 -> 1 -> 2 -> 3 at price 400. It uses 3 flights, but k = 1 allows only 2 flights. The right answer is 700.

So each round keeps a list of prices by number of flights. After round i, prices[v] is the cheapest price of a route from src to v with at most i flights. The list has one number per city, so the cost of a round is small.

A round reads prices and writes tmp_prices. This keeps the rounds apart, so one round can add exactly one flight.

One trace: two rounds on the LeetCode example

The board runs bellman_ford_k_stops with n = 4, src = 0, dst = 3 and k = 1. One stop means at most 2 flights, so there are 2 rounds.

Round 1 starts with prices = [0, inf, inf, inf]. Only flight 0 -> 1 has a reachable start, so tmp_prices[1] = 100. The result is [0, 100, inf, inf].

Round 2 relaxes flight 1 -> 2 to 200 and flight 1 -> 3 to 700. Flight 2 -> 3 is skipped, because prices[2] was still inf at the end of round 1. The result is [0, 100, 200, 700], and the answer is 700.

The step marked as a trap shows what in-place writing would do. tmp_prices[2] = 200 is already set in round 2, so flight 2 -> 3 would give 400. That route has 3 flights, which k = 1 does not allow.

The trap: relaxing in place

tmp_prices = prices does not copy the list. It gives the same list a second name. A price written in a round is then read again in the same round, so one round can add several flights.

For the trace above, writing in place returns 400 and not 700. Flight 0 -> 1 sets prices[1] = 100, flight 1 -> 2 then sets prices[2] = 200, and flight 2 -> 3 sets prices[3] = 400. All three happen in one round.

The second trap is the round count. k stops allow k + 1 flights, so run range(k + 1) rounds. With range(k) the same example returns -1, because the route to city 3 needs both flights.

Last, if prices[dst] is still inf at the end, return -1. For n = 3, flights = [[0, 1, 100]], src = 0, dst = 2 and k = 1, no route exists.

Negative weights, and the cost

Dijkstra needs every weight to be zero or more. A rounds method does not settle a node early, so a negative weight is fine here. A later round can still lower any price.

Example: edges = [[0, 1, 2], [0, 2, 3], [2, 1, -2]] from city 0 to city 1. A Dijkstra that never revisits a settled node returns 2. Bellman-Ford with 2 rounds returns 1, through the route 0 -> 2 -> 1 that costs 3 - 2. With 1 round it still returns 2, as it should for a cap of one flight.

Each round copies prices and reads every flight once, so the time is O(k * (V + E)) and the space is O(V). The board has 5 flights and 2 rounds, so it makes 10 flight checks.

With no cap, run n - 1 rounds. A route without a repeated city has at most n - 1 flights. This costs O(V * E), which is slower than Dijkstra. That is the price of allowing negative weights.

60-Second Pitch

"The path has a cap on stops, so Dijkstra does not fit: it settles a node by cost and cannot count flights. I use Bellman-Ford with rounds. k stops allow k + 1 flights, so I run k + 1 rounds. In each round I copy prices, then for every flight I read the old prices[u] and write the better value into the copy. After round i, prices[v] is the cheapest price with at most i flights. I must write to a copy. If I write in place, one round can use several flights, and the cap breaks. The time is O(k * (V + E)) and the space is O(V). I return prices[dst], or -1 if it is still infinity."

Common Questions

Why does Dijkstra fail when the path has a cap on stops?

Dijkstra settles a node by its cost, and a settled node is never revisited. The cheapest route to a node may use too many stops, and a more expensive route with fewer stops may be the only one the cap allows. Dijkstra keeps one value per node and cannot tell them apart.

Why k + 1 rounds and not k?

k stops are cities between the start and the end, so a route with k stops has k + 1 flights. Each round adds exactly one flight. With k rounds the last flight is never added.

Why copy prices in every round?

The copy keeps rounds apart. If you write in place, a price set earlier in a round is read by a later flight in the same round. One round then adds several flights, and a route with too many stops can win.

What are the time and space complexity?

O(k * (V + E)) time, because each round copies V prices and reads E flights. O(V) space for two lists of prices. With no cap, k + 1 becomes V - 1 rounds, so the time is O(V * E).

Why does this work with negative weights?

No node is settled early. Every round tries every flight again, so a later round can lower any price. Dijkstra assumes a popped distance is final, and a negative edge breaks that. A negative cycle still breaks the answer when there is no cap.

SWE Reasoning

Spot it

  • The question asks for the cheapest path, and the path may use at most k stops, flights or moves.
  • The cheapest path overall may be a path that the cap forbids.
  • Some edge weights can be negative.
  • The graph is small enough for k times the number of edges to be fast.
  • The answer is for one source and one destination, with -1 when no route fits.

The invariant, step by step

  1. prices[src] = 0 and all other prices are infinity. This is the state with 0 flights.
  2. k stops allow k + 1 flights, so run exactly k + 1 rounds.
  3. Each round starts with tmp_prices = prices.copy(). The round reads prices and writes tmp_prices.
  4. For each flight u -> v, skip it if prices[u] is infinity. Otherwise keep prices[u] + price in tmp_prices[v] when it is cheaper.
  5. prices = tmp_prices publishes the round. Every route of at most i flights is a route of at most i - 1 flights plus one last flight, so prices[v] is now the cheapest price with at most i flights.
  6. After the last round, return prices[dst], or -1 if it is still infinity.

Traps

  • Writing into prices in place. One round can chain several flights, and a cheap route with too many stops wins. Write into a copy.
  • range(k) rounds. k stops are k + 1 flights. Run range(k + 1).
  • Reading prices[dst] without a check. It may still be infinity. Return -1 then.
  • Using Dijkstra with a cap. A cheapest route overall may use too many flights. Dijkstra cannot count them.
  • Skipping the infinity check. Adding a price to infinity is harmless in Python. In Java or C++, a large integer can overflow, so skip those flights.

Practice

Canonical LeetCode:Cheapest Flights Within K Stops (LC 787)Minimum Cost to Reach Destination in Time (LC 1928)
Theory Context•Graph Algorithms

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

P3MediumBuilt on Dijkstra~4 min speedrun

Bellman-Ford (K-Stop Relaxation)

Built on
DijkstraEdges are relaxed in rounds instead of popping the cheapest node, so a path is limited to a number of edges and negative weights are allowed.
The problem

Cheapest Flights Within K Stops (LC 787)(opens leetcode.com)

Picture an airline network over n cities, numbered 0 to n - 1. Each entry [from_i, to_i, price_i] of flights is a one-way flight: it leaves from_i, lands in to_i, and its ticket costs price_i.

You want to travel from city src to city dst and pay as little as possible in total, but you accept at most k stops: a stop is a city you land in on the way, other than src and dst, so the trip uses at most k + 1 flights. Return the lowest total ticket price of such a trip, or -1 when no trip with at most k stops gets you to dst.

Example 1
Input:n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1
Output:700
Flying 0 -> 1 -> 3 lands in one city on the way (1 stop) and costs 100 + 600 = 700. Going 0 -> 1 -> 2 -> 3 would cost only 400, but it lands in two cities on the way, one more than k = 1 allows.
1001001006002000123

Bellman-Ford

edges ➔ k + 1 rounds of relaxation on a copy ➔ cheapest price within k stops
Spot it when: You need the cheapest path when the number of edges is capped (at most k stops, flights or moves), or when some edge weights are negative, so Dijkstra's settled-means-final rule no longer holds.
Bellman-Ford
Python
1from typing import List
2 
3def bellman_ford_k_stops(n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
4 """Bellman-Ford (Cheapest Flights Within K Stops): lowest total fare of a trip src
5 -> dst that lands in no more than k cities on the way, or -1.
6 Time: O(K · (V + E)) | Space: O(V)
7 """
8 prices = [float("inf")] * n
9 prices[src] = 0 # 0 flights: only src is reached
10 # k stops = at most k + 1 flights, one more per round
11 for _ in range(k + 1):
12 tmp_prices = prices.copy() # write here, read last round's prices
13 for u, v, price in flights:
14 if prices[u] == float("inf"):
15 # no route from the earlier rounds reaches u
16 continue
17 # one more flight, from last round's price
18 if prices[u] + price < tmp_prices[v]:
19 tmp_prices[v] = prices[u] + price
20 prices = tmp_prices # the round is done: publish it
21 return prices[dst] if prices[dst] != float("inf") else -1
Invariant: After round i, prices[v] is the cheapest price of a route from src to v that uses at most i flights, and a round only ever reads prices and writes tmp_prices.

A route with at most k stops has at most k + 1 flights, so run exactly k + 1 rounds and let each round add one more flight: for every flight u -> v, keep prices[u] + price in tmp_prices[v] when it is cheaper. This is correct because every route of at most i flights is a route of at most i - 1 flights, whose cheapest price is already in prices, plus one last flight, and round i tries every possible last flight; so after round i, prices[v] is the cheapest route to v of at most i flights. The trap is relaxing in place: a round must read the previous round's prices and write into its copy, tmp_prices = prices.copy(). Written straight into prices, a flight relaxed earlier in the round feeds the next flight in the same round, one round chains several flights, and a cheaper route with too many stops wins.

See it run

Bellman-Ford with at most 1 stops, from city 0 to city 3, step by step

round 0 of 2 · prices [0, inf, inf, inf] · tmp_prices -

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, inf, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, inf, inf, inf] · tmp_prices [0, 100, inf, inf]

round 1 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, inf, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, inf, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, inf, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, inf]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, 700]

round 2 of 2 · prices [0, 100, inf, inf] · tmp_prices [0, 100, 200, 700]

round 2 of 2 · prices [0, 100, 200, 700] · tmp_prices [0, 100, 200, 700]

round 2 of 2 · prices [0, 100, 200, 700] · tmp_prices [0, 100, 200, 700] · answer 700

Column j is city j. The top row is prices. The bottom row is tmp_prices, the copy that one round writes to.

  • read prices[u], the price from the last round
  • target tmp_prices[v], which the flight tries to lower
  • written a value just set
  • answer prices[dst], the final answer

prices[0] = 0 and every other city is inf. 1 stop allows at most 2 flights, so run 2 rounds.

Round 1. tmp_prices = prices.copy(). The round reads prices and writes only tmp_prices.

Flight 0 to 1 costs 100. prices[0] + 100 = 100 is less than tmp_prices[1] = inf. So tmp_prices[1] = 100.

Flight 1 to 2 costs 100. prices[1] is inf, so skip it. Reading tmp_prices[1] = 100 would give 200, but that route uses more than 1 flight.

Flight 2 to 0 costs 100. prices[2] is inf, so no route from the earlier rounds reaches 2. Skip it.

Flight 1 to 3 costs 600. prices[1] is inf, so skip it. Reading tmp_prices[1] = 100 would give 700, but that route uses more than 1 flight.

Flight 2 to 3 costs 200. prices[2] is inf, so no route from the earlier rounds reaches 2. Skip it.

Round 1 is done. prices = tmp_prices. Now prices[v] is the cheapest route to v with at most 1 flight.

Round 2. tmp_prices = prices.copy(). The round reads prices and writes only tmp_prices.

Flight 0 to 1 costs 100. prices[0] + 100 = 100 is not less than tmp_prices[1] = 100. Keep it.

Flight 1 to 2 costs 100. prices[1] + 100 = 200 is less than tmp_prices[2] = inf. So tmp_prices[2] = 200.

Flight 2 to 0 costs 100. prices[2] is inf, so no route from the earlier rounds reaches 2. Skip it.

Flight 1 to 3 costs 600. prices[1] + 600 = 700 is less than tmp_prices[3] = inf. So tmp_prices[3] = 700.

Flight 2 to 3 costs 200. prices[2] is inf, so skip it. Reading tmp_prices[2] = 200 would give 400, but that route uses more than 2 flights.

Round 2 is done. prices = tmp_prices. Now prices[v] is the cheapest route to v with at most 2 flights.

All 2 rounds are done. prices[3] is 700, so the answer is 700.

prices
city 00written
city 1inf
city 2inf
city 3inf
tmp_prices
1 / 16
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing into prices in place instead of a copy

    Mistake
    tmp_prices = prices
    Fix
    tmp_prices = prices.copy()

    What happens: tmp_prices = prices makes a second name for the same list. A price written in a round is then read again in the same round. So one round can add several flights. A cheap route with too many stops wins. With no stop limit, relaxing in place is still correct, and only finishes sooner.

    Example: Cheapest Flights Within K Stops (LC 787): n = 4, flights = [[0, 1, 100], [1, 2, 100], [2, 0, 100], [1, 3, 600], [2, 3, 200]], src = 0, dst = 3, k = 1
    Mistake: returns 400Fix: returns 700
  2. 2

    Gotcha 2: Running range(k) rounds instead of range(k + 1)

    Mistake
    for _ in range(k):
    Fix
    for _ in range(k + 1):

    What happens: k stops means up to k + 1 flights. Each round adds exactly one flight. With range(k), the last flight is never added. Routes that use all k stops are missed.

    Example: n = 4, flights = [[0, 1, 100], [1, 2, 100], [2, 0, 100], [1, 3, 600], [2, 3, 200]], src = 0, dst = 3, k = 1
    Mistake: returns -1Fix: returns 700
  3. 3

    Gotcha 3: Returning prices[dst] when it is still float("inf")

    Mistake
    return prices[dst]
    Fix
    return prices[dst] if prices[dst] != float("inf") else -1

    What happens: prices[dst] stays float("inf") when no route with at most k stops reaches dst. The problem asks for -1 then.

    Example: n = 3, flights = [[0, 1, 100]], src = 0, dst = 2, k = 1
    Mistake: returns infFix: returns -1
  4. 4

    Gotcha 4: Dropping the prices[u] == INT_MAX check in Java or C++

    Mistake
    if (prices[u] + price < tmp[v]) {
    Fix
    if (prices[u] == INT_MAX) continue;
    if (prices[u] + price < tmp[v]) {

    What happens: In Python, float("inf") + price is still inf, so the check changes nothing. That is why this mistake has no example. In Java or C++, an unreached city often holds INT_MAX. INT_MAX + price overflows to a negative number. That number looks like the cheapest price of all.

Not this one if…
  • Nothing caps the number of edges and every weight is non-negative, as in Network Delay Time (LC 743): switch to Dijkstra. See Dijkstra
  • You need the shortest distance between every pair of cities, as in Find the City With the Smallest Number of Neighbors at a Threshold Distance (LC 1334), not the cheapest route from one source. See Floyd-Warshall

Deep Dive

What changes from Dijkstra: rounds on a copy, not a heap

The Dijkstra card keeps a heap and settles one node at a time, by cost. This card has no heap. It runs rounds, and round i adds one more flight to every route.

Why this is needed: Dijkstra settles a node by its cost alone, and it cannot count flights. In Cheapest Flights Within K Stops (LC 787), the cheapest route overall may use too many stops. For the example in the board below, Dijkstra would find the route 0 -> 1 -> 2 -> 3 at price 400. It uses 3 flights, but k = 1 allows only 2 flights. The right answer is 700.

So each round keeps a list of prices by number of flights. After round i, prices[v] is the cheapest price of a route from src to v with at most i flights. The list has one number per city, so the cost of a round is small.

A round reads prices and writes tmp_prices. This keeps the rounds apart, so one round can add exactly one flight.

One trace: two rounds on the LeetCode example

The board runs bellman_ford_k_stops with n = 4, src = 0, dst = 3 and k = 1. One stop means at most 2 flights, so there are 2 rounds.

Round 1 starts with prices = [0, inf, inf, inf]. Only flight 0 -> 1 has a reachable start, so tmp_prices[1] = 100. The result is [0, 100, inf, inf].

Round 2 relaxes flight 1 -> 2 to 200 and flight 1 -> 3 to 700. Flight 2 -> 3 is skipped, because prices[2] was still inf at the end of round 1. The result is [0, 100, 200, 700], and the answer is 700.

The step marked as a trap shows what in-place writing would do. tmp_prices[2] = 200 is already set in round 2, so flight 2 -> 3 would give 400. That route has 3 flights, which k = 1 does not allow.

The trap: relaxing in place

tmp_prices = prices does not copy the list. It gives the same list a second name. A price written in a round is then read again in the same round, so one round can add several flights.

For the trace above, writing in place returns 400 and not 700. Flight 0 -> 1 sets prices[1] = 100, flight 1 -> 2 then sets prices[2] = 200, and flight 2 -> 3 sets prices[3] = 400. All three happen in one round.

The second trap is the round count. k stops allow k + 1 flights, so run range(k + 1) rounds. With range(k) the same example returns -1, because the route to city 3 needs both flights.

Last, if prices[dst] is still inf at the end, return -1. For n = 3, flights = [[0, 1, 100]], src = 0, dst = 2 and k = 1, no route exists.

Negative weights, and the cost

Dijkstra needs every weight to be zero or more. A rounds method does not settle a node early, so a negative weight is fine here. A later round can still lower any price.

Example: edges = [[0, 1, 2], [0, 2, 3], [2, 1, -2]] from city 0 to city 1. A Dijkstra that never revisits a settled node returns 2. Bellman-Ford with 2 rounds returns 1, through the route 0 -> 2 -> 1 that costs 3 - 2. With 1 round it still returns 2, as it should for a cap of one flight.

Each round copies prices and reads every flight once, so the time is O(k * (V + E)) and the space is O(V). The board has 5 flights and 2 rounds, so it makes 10 flight checks.

With no cap, run n - 1 rounds. A route without a repeated city has at most n - 1 flights. This costs O(V * E), which is slower than Dijkstra. That is the price of allowing negative weights.

60-Second Pitch

"The path has a cap on stops, so Dijkstra does not fit: it settles a node by cost and cannot count flights. I use Bellman-Ford with rounds. k stops allow k + 1 flights, so I run k + 1 rounds. In each round I copy prices, then for every flight I read the old prices[u] and write the better value into the copy. After round i, prices[v] is the cheapest price with at most i flights. I must write to a copy. If I write in place, one round can use several flights, and the cap breaks. The time is O(k * (V + E)) and the space is O(V). I return prices[dst], or -1 if it is still infinity."

Common Questions

Why does Dijkstra fail when the path has a cap on stops?

Dijkstra settles a node by its cost, and a settled node is never revisited. The cheapest route to a node may use too many stops, and a more expensive route with fewer stops may be the only one the cap allows. Dijkstra keeps one value per node and cannot tell them apart.

Why k + 1 rounds and not k?

k stops are cities between the start and the end, so a route with k stops has k + 1 flights. Each round adds exactly one flight. With k rounds the last flight is never added.

Why copy prices in every round?

The copy keeps rounds apart. If you write in place, a price set earlier in a round is read by a later flight in the same round. One round then adds several flights, and a route with too many stops can win.

What are the time and space complexity?

O(k * (V + E)) time, because each round copies V prices and reads E flights. O(V) space for two lists of prices. With no cap, k + 1 becomes V - 1 rounds, so the time is O(V * E).

Why does this work with negative weights?

No node is settled early. Every round tries every flight again, so a later round can lower any price. Dijkstra assumes a popped distance is final, and a negative edge breaks that. A negative cycle still breaks the answer when there is no cap.

SWE Reasoning

Spot it

  • The question asks for the cheapest path, and the path may use at most k stops, flights or moves.
  • The cheapest path overall may be a path that the cap forbids.
  • Some edge weights can be negative.
  • The graph is small enough for k times the number of edges to be fast.
  • The answer is for one source and one destination, with -1 when no route fits.

The invariant, step by step

  1. prices[src] = 0 and all other prices are infinity. This is the state with 0 flights.
  2. k stops allow k + 1 flights, so run exactly k + 1 rounds.
  3. Each round starts with tmp_prices = prices.copy(). The round reads prices and writes tmp_prices.
  4. For each flight u -> v, skip it if prices[u] is infinity. Otherwise keep prices[u] + price in tmp_prices[v] when it is cheaper.
  5. prices = tmp_prices publishes the round. Every route of at most i flights is a route of at most i - 1 flights plus one last flight, so prices[v] is now the cheapest price with at most i flights.
  6. After the last round, return prices[dst], or -1 if it is still infinity.

Traps

  • Writing into prices in place. One round can chain several flights, and a cheap route with too many stops wins. Write into a copy.
  • range(k) rounds. k stops are k + 1 flights. Run range(k + 1).
  • Reading prices[dst] without a check. It may still be infinity. Return -1 then.
  • Using Dijkstra with a cap. A cheapest route overall may use too many flights. Dijkstra cannot count them.
  • Skipping the infinity check. Adding a price to infinity is harmless in Python. In Java or C++, a large integer can overflow, so skip those flights.

Practice

Canonical LeetCode:Cheapest Flights Within K Stops (LC 787)Minimum Cost to Reach Destination in Time (LC 1928)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•