Skip to content

787. Cheapest Flights Within K Stops (Bellman-Ford, Single-Source Shortest Path with a Limited Number of Edges)

Edited by Charles

Medium · Python · Graph

Problem

Read the original problem on LeetCode.

The official page contains the problem statement, examples, and constraints.

Explanation, examples & other approaches

Core idea

This problem can be solved using a modified Bellman-Ford algorithm.

The key difference from standard Bellman-Ford is that we are only allowed to use at most k stops.

Original study notes follow, including intermediate snippets and earlier attempts.

Approach: Bellman-Ford with Limited Relaxations

This problem can be solved using a modified Bellman-Ford algorithm.

The key difference from standard Bellman-Ford is that we are only allowed to use at most k stops.

If a path contains k intermediate stops, then it contains at most:

k + 1 edges

Therefore, instead of performing n - 1 rounds of relaxation, we only perform:

k + 1

rounds.

Store All Edges

We first store all flights as edges:

edges = []

for u, v, w in flights:
    edges.append((u, v, w))

Each edge represents:

(source, destination, cost)

Since Bellman-Ford directly iterates through all edges, we do not need to build an adjacency list.

Define minCost

minCost = [float('inf')] * n
minCost[src] = 0

minCost[i] represents:

The minimum cost to reach city i from src using at most the number of edges allowed by the current relaxation round.

Initially:

src          → 0
other cities → infinity

because we have not used any flights yet.

Why k + 1 Relaxations?

The problem allows at most k stops.

For example:

src → A → B → dst

There are:

2 stops: A, B
3 edges

Therefore:

k stops = k + 1 edges

Bellman-Ford has an important property:

After 1 round → shortest paths using at most 1 edge
After 2 rounds → shortest paths using at most 2 edges
After 3 rounds → shortest paths using at most 3 edges
...

Therefore, to allow at most k stops, we perform:

for _ in range(k + 1):

This is the most important part of this problem.

At the beginning of every round:

temp = minCost.copy()

We read values from:

minCost

but write new values into:

temp

During relaxation:

if minCost[source] + cost < temp[destination]:
    temp[destination] = minCost[source] + cost

At the end of the round:

minCost = temp

The purpose of temp is to make sure that each Bellman-Ford round extends the path by at most one edge.

What Goes Wrong Without temp?

Consider:

0 --100--> 1 --100--> 2 --100--> 3

Initially:

minCost = [0, inf, inf, inf]

Suppose we directly update minCost in place.

Process 0 → 1

minCost[1] = 100

[0, 100, inf, inf]

Process 1 → 2

Because minCost[1] was just updated in the same round, we can immediately use it:

minCost[2] = 200

[0, 100, 200, inf]

Process 2 → 3

Again, minCost[2] was just updated:

minCost[3] = 300

[0, 100, 200, 300]

This means that in one relaxation round, we traveled:

0 → 1 → 2 → 3

which uses 3 edges.

This destroys the meaning of:

1 round = at most 1 additional edge

and can violate the k-stop constraint.

Now consider the same graph:

0 --100--> 1 --100--> 2 --100--> 3

Initially:

minCost = [0, inf, inf, inf]

At the beginning of the first round:

temp = minCost.copy()

so:

minCost = [0, inf, inf, inf]
temp    = [0, inf, inf, inf]

Round 1

Process:

0 → 1

We update:

temp[1] = 100

so:

minCost = [0, inf, inf, inf]
temp    = [0, 100, inf, inf]

Now process:

1 → 2

The important point is that we check:

minCost[1]

NOT:

temp[1]

Since:

minCost[1] = inf

we cannot use the newly discovered path to node 1 in the same round.

Therefore, node 2 is not updated.

After the round:

minCost = temp

Now:

minCost = [0, 100, inf, inf]

This represents paths using at most 1 edge.

Round 2

Create another copy:

minCost = [0, 100, inf, inf]
temp    = [0, 100, inf, inf]

Now:

1 → 2

can use:

minCost[1] = 100

so:

temp[2] = 200

After this round:

minCost = [0, 100, 200, inf]

This represents paths using at most 2 edges.

Round 3

Similarly:

minCost = [0, 100, 200, inf]

allows:

2 → 3

so:

minCost = [0, 100, 200, 300]

This represents paths using at most 3 edges.

Therefore:

Round 1 → at most 1 edge
Round 2 → at most 2 edges
Round 3 → at most 3 edges
...
Round k+1 → at most k+1 edges

This is exactly what we need.

Read from minCost, Write to temp

The key relaxation is:

if minCost[source] != float('inf') and \
   minCost[source] + cost < temp[destination]:

    temp[destination] = minCost[source] + cost

Notice the difference:

Read source distance:        minCost[source]
Write destination distance:  temp[destination]

This is intentional.

minCost represents the results from the previous round.

temp represents the results for the current round.

Therefore:

Previous round
    minCost
       ↓
use one more edge
       ↓
     temp
       ↓
end of round
       ↓
minCost = temp

This guarantees that one round can add at most one new edge to a path.

Why temp = minCost.copy() Instead of a New inf Array?

We use:

temp = minCost.copy()

instead of:

temp = [float('inf')] * n

because the problem asks for paths using at most k + 1 edges, not exactly k + 1 edges.

For example, if we already know:

src → A
cost = 100

after one round, that result should still be valid after the second round.

Copying:

temp = minCost.copy()

preserves all previously discovered cheaper paths while also allowing paths using one additional edge to be discovered.

Therefore:

Round i stores the minimum cost using at most i edges.

Early Termination

We use:

updated = False

and whenever a better path is found:

updated = True

After scanning all edges:

if not updated:
    break

If no value changes during a complete relaxation round, further rounds cannot improve any shortest path.

Therefore, we can terminate early.

Complexity

Let:

V = number of cities
E = number of flights

We perform at most:

k + 1

rounds.

Each round scans all E edges.

Therefore:

Time: O((k + 1) * E)

The minCost and temp arrays require:

O(V)

space.

The edges list requires:

O(E)

space.

Therefore:

Space: O(V + E)

Since flights itself already contains all edges, the extra algorithmic distance-array space is O(V).

Code

class Solution:
    def findCheapestPrice(
        self,
        n: int,
        flights: List[List[int]],
        src: int,
        dst: int,
        k: int
    ) -> int:

        edges = []

        for u, v, w in flights:
            edges.append((u, v, w))

        minCost = [float('inf')] * n
        minCost[src] = 0

        for _ in range(k + 1):
            updated = False
            temp = minCost.copy()

            for source, destination, cost in edges:
                if (
                    minCost[source] != float('inf')
                    and minCost[source] + cost < temp[destination]
                ):
                    temp[destination] = minCost[source] + cost
                    updated = True

            if not updated:
                break

            minCost = temp

        return -1 if minCost[dst] == float('inf') else minCost[dst]

Key Takeaway

The most important idea in this problem is:

temp prevents updates from the current relaxation round from being used again in the same round.

Without temp:

One round may use multiple edges
→ cannot enforce the k-stop constraint

With temp:

Read from minCost
       ↓
Use exactly one additional edge
       ↓
Write to temp
       ↓
minCost = temp

Therefore:

1 round     → at most 1 edge
2 rounds    → at most 2 edges
...
k + 1 rounds → at most k + 1 edges

Since:

k stops = k + 1 edges

performing k + 1 Bellman-Ford relaxation rounds correctly enforces the constraint.

Study record
Field Value
Attempts 1
Mastery ❚
The Latest Attempt August 31, 2026

Python solution

class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        INF = float('inf')
        dist = [INF] * n
        dist[src] = 0

        for _ in range(k + 1):
            new = dist[:]
            for u, v, w in flights:
                if dist[u] + w < new[v]:
                    new[v] = dist[u] + w
            dist = new

        return dist[dst] if dist[dst] != INF else -1

Back to Day 53 Graph XI · All Algorithm notes