Graph · 32 of 43
Cheapest Flights Within K Stops
Find the cheapest trip between two cities when you may change planes only a few times.
- Simple way
- time O(n^(k+1))
room O(n + E) - Clever way
- time O(k * E)
room O(n)
The problem, simply
There are n cities. Each flight [from, to, price] goes one way and costs some money. You start in city src and want to reach city dst.
You may stop in at most k cities on the way. So you may take at most k + 1 flights. Give back the cheapest total price, or -1 if no trip fits the rule.
A story to remember it
Picture it
Flights go one way. 0 to 1 to 3 costs 700 with 1 stop. 0 to 1 to 2 to 3 costs 400 but needs 2 stops.
- looking now
- answer
Examples
One stop allowed
- In
n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1- Out
700
Only 0, 1, 3 fits in one stop. The cheaper trip through 2 needs two stops.
Two stops allowed
- In
same flights, src = 0, dst = 3, k = 2- Out
400
Now 0, 1, 2, 3 is allowed: 100 + 100 + 200 = 400.
The cheap trip has too many stops
- In
n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 0- Out
500
With zero stops you must fly direct. The 200 trip through city 1 is not allowed. Plain Dijkstra would wrongly say 200.
No way there
- In
n = 3, flights = [[0,1,100]], src = 0, dst = 2, k = 5- Out
-1
No flight ever lands in city 2, no matter how many stops you allow.
Way 1: the simple way
Try every trip. Start at src, take any flight, then any flight from there, and keep going while you still have flights left. Each time you land on dst, remember the price if it is the cheapest so far.
- Build a list of flights leaving each city.
- Walk deeper and deeper with a count of flights left, carrying the price so far.
- Stop a path when it reaches
dst, runs out of flights, or already costs more than the best answer. - Do not visit a city twice on one path. With positive prices a loop can never help.
class CheapestFlightsDfs {
static int best;
public static int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
List<List<int[]>> out = new ArrayList<>();
for (int i = 0; i < n; i++) out.add(new ArrayList<>());
for (int[] f : flights) out.get(f[0]).add(new int[]{f[1], f[2]});
best = Integer.MAX_VALUE;
boolean[] onPath = new boolean[n];
onPath[src] = true;
dfs(out, src, dst, k + 1, 0, onPath); // k stops means k + 1 flights
return best == Integer.MAX_VALUE ? -1 : best;
}
static void dfs(List<List<int[]>> out, int city, int dst, int flightsLeft, int cost, boolean[] onPath) {
if (cost >= best) return; // already too pricey
if (city == dst) { best = cost; return; }
if (flightsLeft == 0) return;
for (int[] f : out.get(city)) {
int next = f[0], price = f[1];
if (onPath[next]) continue;
onPath[next] = true;
dfs(out, next, dst, flightsLeft - 1, cost + price, onPath);
onPath[next] = false; // step back and try another flight
}
}
}How long it takes: each step can branch to up to n cities and a path has up to k + 1 steps, so up to O(n^(k+1)) paths. Extra room: the flight lists O(n + E) and a path of depth k + 1.
Way 2: the clever way
Play the hopping game in rounds. After round r, price[c] is the cheapest way to reach city c using at most r flights. Each round looks at every flight once.
- Set
price[src] = 0and every other price to infinity. - Repeat
k + 1times: - Copy the prices into
next. For each flightutovwith costw, ifprice[u]is known andprice[u] + w < next[v], setnext[v] = price[u] + w. - Replace
pricewithnext. At the end,price[dst]is the answer, or-1if it is still infinity.
The copy is the whole trick. We read from last round's prices and write to this round's prices, so one round can only add one flight.
Two rounds for k = 1, reading only last round's prices
01 / 10
Before any flight, only city 0 has a price: 0. Every other city costs ∞. A round lets everyone take one more flight.
- flights allowed
- 2
- looking now
- checking
- done
- answer
- rejected
- just added
- ignored
class CheapestFlightsRounds {
public static int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
final int INF = Integer.MAX_VALUE;
int[] price = new int[n];
Arrays.fill(price, INF);
price[src] = 0;
for (int round = 0; round <= k; round++) { // k + 1 rounds, one flight each
int[] next = Arrays.copyOf(price, n); // write here, read from price
for (int[] f : flights) {
int u = f[0], v = f[1], w = f[2];
if (price[u] != INF && price[u] + w < next[v]) {
next[v] = price[u] + w;
}
}
price = next;
}
return price[dst] == INF ? -1 : price[dst];
}
public static void main(String[] args) {
int[][] flights = {{0, 1, 100}, {1, 2, 100}, {2, 0, 100}, {1, 3, 600}, {2, 3, 200}};
System.out.println(findCheapestPrice(4, flights, 0, 3, 1)); // 700
System.out.println(findCheapestPrice(4, flights, 0, 3, 2)); // 400
}
}How long it takes: k + 1 rounds, each looks at all E flights, O(k * E). Extra room: two price arrays of size n, O(n).
Watch out for
kcounts stops, not flights. Runk + 1rounds.- Skip flights that start from a city with price infinity, or
INF + wwraps around to a negative number. - Plain Dijkstra on price alone is wrong: a cheap path with too many stops can settle a city before the pricier path with few stops arrives.
- If
src == dst, the answer is 0 with no flights at all.
What interviewers ask next
- Can Dijkstra work here? Yes, if the heap holds
{price, city, flightsUsed}and you allow a city to be visited again when it is reached with fewer flights. KeepbestFlights[city]and skip a pop only if it used more flights than a previous pop of that city. - Can you do it as BFS? Yes. Go level by level with a queue, one level per flight, and stop after
k + 1levels. Keep the best price per city and only push improvements. - How do you return the route too? Keep a parent per city per round, or store the path inside the queue entries.
- What if there were no stop limit? Then it is a plain shortest path with positive prices: use Dijkstra.
Remember this
Grown-up words: cities are vertices, flights are directed weighted edges, a round is one Bellman-Ford relaxation pass, the copy keeps the rounds layered so round r uses at most r edges, and the hopping game is dynamic programming over the number of edges used.