LeetCode #787 Medium

Cheapest Flights Within K Stops

Cheapest Flights Within K Stops is LeetCode 787 (Medium). There are n cities and a list flights where flights[i] = [from, to, price] is a one-way flight. Return the cheapest price from src to dst using at most k stops, or -1 if there is no such route.

  • k stops means at most k + 1 flights.
  • Prices are positive; n is at most 100.
  • The cheapest route overall may use too many stops, so a plain shortest path is not enough.
Constraints
  • 2 <= n <= 100
  • 0 <= flights.length <= (n * (n - 1) / 2)
  • flights[i].length == 3
  • 0 <= fromi, toi < n
  • fromi != toi
  • 1 <= pricei <= 10⁴
  • There will not be any multiple flights between two cities.
  • 0 <= src, dst, k < n
  • src != dst
graphsshortest-pathbellman-fordbfs
Open on LeetCode ↗
02

Intuition

Without the stop limit this is a normal shortest path. The limit adds a second rule: a route may use at most k + 1 flights.

Bellman-Ford handles that rule naturally because it works in rounds. After round r, every city holds the cheapest price using at most r flights. So run exactly k + 1 rounds and read the price of dst. The only care needed is that each round extends a route by one flight, never more.

How to spot this pattern

"Shortest path with at most k edges" is Bellman-Ford with the number of rounds as the limit, using a copy of the distances each round. If the limit were on something else, such as fuel or time, the usual fix is to put it in the state, as in the Dijkstra version below, so each (city, used) pair is its own node.

03

Approach

Try it first

Before reading on: in the first example, the cheapest route from 0 to 3 costs 400 but uses three flights. With k = 1, which route is allowed, and what would make an algorithm pick the 400 route by mistake?

1

Start with the source only

Set dist[src] = 0 and every other city to infinity. These are the cheapest prices using zero flights: only the source is reachable.

2

Run k + 1 rounds on a copy

Each round starts with nxt = dist[:]. Read prices from dist, write them into nxt. Since dist only holds routes from the previous round, every route can grow by exactly one flight per round.

3

Try every flight

For each flight u → v with price w:

  • dist[u] is infinity: skip, u was not reachable last round.
  • dist[u] + w < nxt[v]: a cheaper route to v, so store it.
  • otherwise: keep the current price.
4

Finish the round, then answer

Set dist = nxt. After k + 1 rounds, dist[dst] is the cheapest price with at most k stops, or infinity if dst was never reached, in which case return -1. The time is O(k · E).

04

Cheapest Flights Within K Stops solution in Python | C++ | Java

▶1class Solution:
▶2 def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
▶3 INF = float("inf")
▶4 dist = [INF] * n
▶5 dist[src] = 0
▶6 for _ in range(k + 1):
▶7 nxt = dist[:]
▶8 for u, v, w in flights:
▶9 if dist[u] != INF and dist[u] + w < nxt[v]:
▶10 nxt[v] = dist[u] + w
▶11 dist = nxt
▶12 return -1 if dist[dst] == INF else dist[dst]
1001001006002000src123dstdistnextcity 00city 1∞city 2∞city 3∞dist[0] = 0, all others ∞
dist[0, ∞, ∞, ∞]
k1stops allowed
rounds2flights allowed
Start. With zero flights only the source is reachable, at price 0. k = 1 stops means at most 2 flights, so the loop runs 2 rounds, each allowing one more flight.
1001001006002000src123dstdistnextcity 000city 1∞∞city 2∞∞city 3∞∞round 1: next starts as a copy of dist
round1≤ 1 flight
next[0, ∞, ∞, ∞]
Round 1. Copy dist into next. Every flight below reads prices from dist, which so far only holds the source, and writes to next. That way a route can grow by one flight per round and never more.
1001001006002000src123dstdistnextcity 000city 1∞100city 2∞∞city 3∞∞0 → 1: 0 + 100 < ∞ → 100
flight0 → 1price 100
dist[0]0
next[1]100
Fly 0 → 1 for 100: 0 + 100 = 100, cheaper than the ∞ known for city 1. Write it to next[1]; the route behind it uses at most 1 flight.
1001001006002000src123dstdistnextcity 000city 1∞100city 2∞∞city 3∞∞1 → 2: dist[1] is ∞ → skip
flight1 → 2price 100
dist[1]∞
next[2]∞
City 1 cannot be reached with 0 flights, so this flight cannot extend any route yet. next[1] already holds 100 from this round, but using it (100 + 100 = 200 for city 2) would chain two flights in one round and break the stop limit; that is why the code reads dist.
1001001006002000src123dstdistnextcity 000city 1∞100city 2∞∞city 3∞∞2 → 0: dist[2] is ∞ → skip
flight2 → 0price 100
dist[2]∞
next[0]0
City 2 cannot be reached with 0 flights, so this flight cannot extend any route yet.
1001001006002000src123dstdistnextcity 000city 1∞100city 2∞∞city 3∞∞1 → 3: dist[1] is ∞ → skip
flight1 → 3price 600
dist[1]∞
next[3]∞
City 1 cannot be reached with 0 flights, so this flight cannot extend any route yet. next[1] already holds 100 from this round, but using it (100 + 600 = 700 for city 3) would chain two flights in one round and break the stop limit; that is why the code reads dist.
1001001006002000src123dstdistnextcity 000city 1∞100city 2∞∞city 3∞∞2 → 3: dist[2] is ∞ → skip
flight2 → 3price 200
dist[2]∞
next[3]∞
City 2 cannot be reached with 0 flights, so this flight cannot extend any route yet.
1001001006002000src123dstdistnextcity 00city 1100city 2∞city 3∞dist = next: cheapest with ≤ 1 flight
dist[0, 100, ∞, ∞]
End of round 1. next becomes dist: each price is now the cheapest route using at most 1 flight. The next round may add one more flight.
1001001006002000src123dstdistnextcity 000city 1100100city 2∞∞city 3∞∞round 2: next starts as a copy of dist
round2≤ 2 flights
next[0, 100, ∞, ∞]
Round 2. Copy dist into next. Every flight below reads prices from dist, which only holds routes of up to 1 flight, and writes to next. That way a route can grow by one flight per round and never more.
1001001006002000src123dstdistnextcity 000city 1100100city 2∞∞city 3∞∞0 → 1: 0 + 100 ≥ 100 → skip
flight0 → 1price 100
dist[0]0
next[1]100
Fly 0 → 1 for 100: 0 + 100 = 100 is not cheaper than the 100 already known for city 1, so nothing changes.
1001001006002000src123dstdistnextcity 000city 1100100city 2∞200city 3∞∞1 → 2: 100 + 100 < ∞ → 200
flight1 → 2price 100
dist[1]100
next[2]200
Fly 1 → 2 for 100: 100 + 100 = 200, cheaper than the ∞ known for city 2. Write it to next[2]; the route behind it uses at most 2 flights.
1001001006002000src123dstdistnextcity 000city 1100100city 2∞200city 3∞∞2 → 0: dist[2] is ∞ → skip
flight2 → 0price 100
dist[2]∞
next[0]0
City 2 cannot be reached with 1 flight, so this flight cannot extend any route yet. next[2] already holds 200 from this round, but using it (200 + 100 = 300 for city 0) would chain two flights in one round and break the stop limit; that is why the code reads dist.
1001001006002000src123dstdistnextcity 000city 1100100city 2∞200city 3∞7001 → 3: 100 + 600 < ∞ → 700
flight1 → 3price 600
dist[1]100
next[3]700
Fly 1 → 3 for 600: 100 + 600 = 700, cheaper than the ∞ known for city 3. Write it to next[3]; the route behind it uses at most 2 flights.
1001001006002000src123dstdistnextcity 000city 1100100city 2∞200city 3∞7002 → 3: dist[2] is ∞ → skip
flight2 → 3price 200
dist[2]∞
next[3]700
City 2 cannot be reached with 1 flight, so this flight cannot extend any route yet. next[2] already holds 200 from this round, but using it (200 + 200 = 400 for city 3) would chain two flights in one round and break the stop limit; that is why the code reads dist.
1001001006002000src123dstdistnextcity 00city 1100city 2200city 3700dist = next: cheapest with ≤ 2 flights
dist[0, 100, 200, 700]
End of round 2. next becomes dist: each price is now the cheapest route using at most 2 flights. No more rounds are allowed.
1001001006002000src123dstdistnextcity 00city 1100city 2200city 3700return 700
dist[3]700
answer700
Answer: 700. This is the cheapest price to city 3 with at most 1 stop. Without the limit, 400 would be possible, but that route needs more flights, so the rounds never let it form.
05

Dijkstra with a stop count

Heap entries carry the flights used so far. The first time dst is popped its price is the answer. A city is expanded again only if it is reached with fewer flights than before, since a later pop is never cheaper.

▶1class Solution:
▶2 def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
▶3 graph = [[] for _ in range(n)]
▶4 for u, v, w in flights:
▶5 graph[u].append((v, w))
▶6 fewest = [float("inf")] * n
▶7 heap = [(0, src, 0)]
▶8 while heap:
▶9 cost, u, used = heapq.heappop(heap)
▶10 if u == dst:
▶11 return cost
▶12 if used >= fewest[u] or used > k:
▶13 continue
▶14 fewest[u] = used
▶15 for v, w in graph[u]:
▶16 heapq.heappush(heap, (cost + w, v, used + 1))
▶17 return -1
06

Common pitfalls

Relaxing in place

✗ Wrong
for u, v, w in flights:
    if dist[u] + w < dist[v]:
        dist[v] = dist[u] + w
✓ Right
nxt = dist[:]
for u, v, w in flights:
    if dist[u] != INF and dist[u] + w < nxt[v]:
        nxt[v] = dist[u] + w
dist = nxt

A price written earlier in the round is read again later in the same round, so one round can add several flights. In the first example this returns 400 (three flights) instead of 700.

Running k rounds instead of k + 1

✗ Wrong
for _ in range(k):
✓ Right
for _ in range(k + 1):

A direct flight has zero stops, so k stops allow k + 1 flights. With k rounds, k = 0 never even tries the direct flights and returns -1.

Dijkstra on price alone

✗ Wrong
heapq.heappush(heap, (cost + w, v))
✓ Right
heapq.heappush(heap, (cost + w, v, used + 1))

Plain Dijkstra keeps only the cheapest price per city. That price may need too many flights, while a pricier route with fewer flights, the only legal one, is thrown away. The number of flights has to be part of the state.

07

Complexity

Time
O(k · E)
Space
O(n)
k + 1 rounds, each checking all E flights. Two price arrays of size n.
08

Cheapest Flights Within K Stops FAQ

Can BFS solve the cheapest flights within k stops LeetCode problem?

Yes. Go level by level, where level r holds the cities reached with r flights, and stop after k + 1 levels. Keep a best price per city and only push a city again when its price improves. It does the same work as the rounds above, organised as a queue.