Find the cheapest one-way flight path from src to dst using at most k stops. The trick is a snapshot-based Bellman-Ford run exactly k+1 times — the snapshot prevents chaining more than one edge per round, which is the whole constraint.
You have n cities and a list of directed flights, each with a cost. Find the cheapest path from src to dst using at most k intermediate stops (so at most k+1 edges). Return -1 if no such path exists.
Worked example. n=3, flights [[0,1,100],[1,2,100],[0,2,500]], src=0, dst=2, k=1. Two paths reach city 2: 0→2 costs 500; 0→1→2 costs 200 and uses 1 stop (within limit). Answer: 200.
k+1 times. After round r, dist[v] holds the cheapest cost reachable in exactly at most r edges — but only if you copy dist into a snapshot before each round and relax using the snapshot. Without the copy, a single round could chain multiple edges (following the path 0→1→2 in one pass), silently violating the k-stop budget.dist[src] = 0 and dist[i] = ∞for all other cities. This represents "0 edges used, only src reachable."k+1 rounds the budget is exhausted.dist into prev at the start of each round. All reads in this round use prev, all writes go to dist. This is the critical step.[from, to, cost]: if prev[from] + cost < dist[to], update dist[to].dist[dst] or -1 if still ∞.dist[from] + cost (live array) instead of prev[from] + cost (snapshot), a single round can chain edges across the whole graph — so you get the cheapest path ignoring the stop limit, not with it. Always take the snapshot.Only two arrays (dist and prev) of length n are needed — O(n) space. A BFS/Dijkstra with (city, stops) state pairs would use O(n·k) space.
2 rounds. Init dist[0] = 0; everything else ∞. Each round uses a snapshot so we can't chain relaxations.1function findCheapestPrice(2 n: number,3 flights: number[][],4 src: number,5 dst: number,6 k: number7): number {8 const INF = Infinity;9 // dist[i] = cheapest cost to reach city i using at most (round) edges so far10▶ let dist: number[] = Array(n).fill(INF);11▶ dist[src] = 0;1213 for (let round = 0; round < k + 1; round++) {14 // Snapshot: each round can only use prices from the PREVIOUS round.15 // Without this copy, a chain of updates within one round would let us16 // traverse more than 1 edge per round — violating the k-stop constraint.17 const prev = [...dist];1819 for (const [from, to, cost] of flights) {20 if (prev[from] === INF) continue; // city not yet reachable21 if (prev[from] + cost < dist[to]) {22 dist[to] = prev[from] + cost;23 }24 }25 }2627 return dist[dst] === INF ? -1 : dist[dst];28}
function findCheapestPrice(
n: number,
flights: number[][],
src: number,
dst: number,
k: number
): number {
const INF = Infinity;
// dist[i] = cheapest cost to reach city i using at most (round) edges so far
let dist: number[] = Array(n).fill(INF);
dist[src] = 0;
for (let round = 0; round < k + 1; round++) {
// Snapshot: each round can only use prices from the PREVIOUS round.
// Without this copy, a chain of updates within one round would let us
// traverse more than 1 edge per round — violating the k-stop constraint.
const prev = [...dist];
for (const [from, to, cost] of flights) {
if (prev[from] === INF) continue; // city not yet reachable
if (prev[from] + cost < dist[to]) {
dist[to] = prev[from] + cost;
}
}
}
return dist[dst] === INF ? -1 : dist[dst];
}dist[src] = 0; every other city starts at Infinity. After zero rounds only the source is reachable with zero cost.k intermediate stops, meaning at most k+1 edges. One loop iteration = one additional edge allowed.const prev = [...dist] freezes the state from the previous round. Every read in this loop uses prev, not dist. Without this, a single iteration could walk an arbitrarily long path, breaking the constraint.from (at last round's cost) gives a cheaper route to to.prev[from] === INF means from was unreachable; skip it to avoid arithmetic on Infinity.k+1 relaxation passes, dist[dst] is the minimum cost reachable within the budget, or -1 if still Infinity.O(k · E) time — k+1 rounds each scanning all E edges. Space is O(n) for the two distance arrays (no graph adjacency structure needed beyond the edge list).parent array and update it whenever you relax an edge. Then trace back from dst to src.O(E log V) — since the stop count is no longer the binding constraint and Dijkstra is faster than O(n · E) Bellman-Ford.O(n·k) state but is often more intuitive to explain.n × n Bellman-Ford DP table (dp[stops][city]) and answer each query in O(1).function findCheapestPrice(
n: number,
flights: number[][],
src: number,
dst: number,
k: number
): number {
const INF = Infinity;
// dist[i] = cheapest cost to reach city i using at most (round) edges so far
let dist: number[] = Array(n).fill(INF);
dist[src] = 0;
for (let round = 0; round < k + 1; round++) {
// Snapshot: each round can only use prices from the PREVIOUS round.
// Without this copy, a chain of updates within one round would let us
// traverse more than 1 edge per round — violating the k-stop constraint.
const prev = [...dist];
for (const [from, to, cost] of flights) {
if (prev[from] === INF) continue; // city not yet reachable
if (prev[from] + cost < dist[to]) {
dist[to] = prev[from] + cost;
}
}
}
return dist[dst] === INF ? -1 : dist[dst];
}O(k · E) time — k+1 rounds each scanning all E edges. Space is O(n) for the two distance arrays (no graph adjacency structure needed beyond the edge list).Build an adjacency list and recursively explore every flight out of the current city, decrementing the remaining-stops budget, and keep the cheapest total cost that reaches dst before the budget runs out.
function findCheapestPrice(
n: number,
flights: number[][],
src: number,
dst: number,
k: number
): number {
const adj: [number, number][][] = Array.from({ length: n }, () => []);
for (const [from, to, cost] of flights) adj[from].push([to, cost]);
let best = Infinity;
// stops = number of intermediate cities still allowed
function dfs(city: number, stops: number, cost: number): void {
if (cost >= best) return; // prune: already worse than known best
if (city === dst) { best = cost; return; }
if (stops < 0) return; // out of budget
for (const [next, price] of adj[city]) {
dfs(next, stops - 1, cost + price);
}
}
dfs(src, k, 0);
return best === Infinity ? -1 : best;
}k and revisits the same (city, stops) states over and over. Bellman-Ford relaxes all edges k + 1 rounds for O(k · E) instead.| "cheapest path with at most k edges/stops" | Bellman-Ford k+1 rounds + snapshot |
| edge count / hop count is the budget | BFS by layers or snapshot Bellman-Ford |
| shortest path on directed weighted graph | Bellman-Ford (negative ok) or Dijkstra (non-negative) |
| "return -1 if unreachable within constraint" | dist[dst] === INF ? -1 : dist[dst] |
let dist: number[] = Array(n).fill(Infinity);
dist[src] = 0;
for (let round = 0; round < k + 1; round++) {
const prev = [...dist]; // snapshot — key step
for (const [from, to, cost] of flights) {
if (prev[from] === Infinity) continue;
if (prev[from] + cost < dist[to]) {
dist[to] = prev[from] + cost;
}
}
}
return dist[dst] === Infinity ? -1 : dist[dst];n=3, flights [[0,1,100],[1,2,100],[0,2,500]], src=0, dst=2, k=1, what is the answer?k=0. What is returned?