DSA sheet · Graphs · Dijkstra pattern
Cheapest Flights Within K Stops
This looks like plain Dijkstra (cheapest route from src to dst), but there's a twist: you may make at most k stops. That one limit breaks the usual "keep only the cheapest arrival" rule, because a more expensive arrival with fewer stops can be the only one that still reaches the destination in time. The teacher first solves it with a min-heap (correct, but it ran out of memory on LeetCode), then shows that a plain BFS queue is enough here and is accepted. This page also adds a third, round-by-round version (Bellman-Ford style), which has the cleanest guarantee.
Every problem below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · What you must know before starting
- Part A · Min-heap with (cost, node, stops), and why cost alone is wrong
- Part B · Plain BFS queue (her accepted solution)
- Part C · Bellman-Ford style rounds (extra)
- Part D · Revision page
Part 0 · Before starting
Directed, weighted graph
Cities are nodes, flights are edges. A flight [from, to, price] is a directed edge (one way: a flight from 0 to 1 does not mean there's one from 1 to 0) with a weight (the price). The cost of a trip is the sum of its flight prices.
Adjacency list
n = 4
flights = [[0, 1, 100], [1, 2, 100], [2, 0, 100], [1, 3, 600], [2, 3, 200]]
graph = [[] for _ in range(n)]
for u, v, price in flights:
graph[u].append((v, price)) # one way only
print(graph) # [[(1, 100)], [(2, 100), (3, 600)], [(0, 100), (3, 200)], []]Stops vs flights
A stop is a city you land in between the start and the destination. The start and the destination don't count, the same way the station you leave from and the station you arrive at aren't "stops" on a train trip.
0 ——→ 1 ——→ 2 ——→ 3
^ ^
stop stop 3 flights = 2 stops
So flights = stops + 1. "At most k stops" means "at most k + 1 flights". In the code, the teacher's counter (she calls it stops) is really the number of flights taken so far: 0 at src, +1 for every flight. A node reached with counter ≤ k may still take one more flight; a node reached with counter k + 1 may not.
BFS layers, visited, and Dijkstra (quick recap)
- BFS with a queue (
collections.deque) goes level by level: all nodes 1 flight away, then 2 flights away… A visited array stops it from going round cycles. In unweighted graphs, the level is the shortest distance. - With prices (weights), fewer flights isn't always cheaper, so plain BFS with visited fails. Dijkstra replaces the queue with a min-heap (always expand the cheapest so far) and replaces visited with "go again only if cheaper". See the Dijkstra page for the full story and a picture of BFS going wrong.
- In Python the heap is
heapq:heappush,heappop, and tuples are ordered by their first item, so(cost, node, stops)sorts by cost.
This problem adds a second number per route, the stops. Dijkstra's "keep only the cheapest" rule thinks about cost only. That's exactly where it goes wrong here (Part A, step 4).
Part A · Min-heap with (cost, node, stops)
LeetCode 787
1The question in simple words
n cities, numbered 0 to n−1. flights[i] = [from, to, price]. Given src, dst and k, return the cheapest price from src to dst using at most k stops. If there's no such route, return −1.
The teacher's picture (rebuilt from the routes and totals she reads out), with src = 0, dst = 3, k = 2:
100
(0) ————————→ (1)
| /
400| 200 /
↓ ↙
(2) ——————500——————→ (3)
\ ↑
100\ |100
↘ |
(4) ————————————┘
| route | price | flights | stops | allowed (k = 2)? |
|---|---|---|---|---|
| 0 → 2 → 3 | 400 + 500 = 900 | 2 | 1 | yes |
| 0 → 1 → 2 → 3 | 100 + 200 + 500 = 800 | 3 | 2 | yes |
| 0 → 2 → 4 → 3 | 400 + 100 + 100 = 600 | 3 | 2 | yes ← cheapest allowed |
| 0 → 1 → 2 → 4 → 3 | 100 + 200 + 100 + 100 = 500 | 4 | 3 | no, 3 stops |
The overall cheapest route (500) is not allowed. The answer is 600.
2What the constraints tell us
n ≤ 100, at mostn(n−1)/2flights → about 5000 edges.k < n→ k can be up to about 100.- Prices up to 104. Even 100 flights × 104 = 106, far below the int limit (about 2·109). Safe (and Python ints never overflow anyway).
- Prices are positive, so heap ideas are allowed.
- k × E ≈ 100 × 5000 = 5·105. She uses this number at the end to explain the memory error.
3Intuition
Do Dijkstra, but let each heap entry also carry how many flights it has used. The heap still pops the cheapest entry first, so the first time dst is popped, that's the cheapest allowed price. Entries that have used up their flights are not expanded any more.
The big change from Dijkstra: don't throw away an arrival just because it is more expensive, if it used fewer stops. It may be the only one with enough stops left to finish.
4Building the logic from her examples
What goes into the heap? → (cost, node, stops)
- cost first, so the min-heap orders by price;
- node, to know where we are and whose flights to look at;
- stops: without it, when we land somewhere we can't tell whether we're still allowed to fly on. Her example: arriving at a non-destination city having already used up the allowed stops means the next flight would break the rule, so we stop there.
Start: (0, src, 0) and dist[src] = 0. Each flight adds the price to cost and 1 to stops.
Rule 1: popping dst → return the cost right away
The heap only ever contains allowed routes (we never push a route with too many flights). It pops them cheapest first. So the first popped entry for dst is the cheapest allowed route. Return it; no need to empty the heap. In her words, in the best or average case you finish early instead of exploring everything.
Rule 2: too many flights → don't expand
If we pop a node that is not dst and stops > k (we've already taken k + 1 flights), one more flight would make k + 1 stops. Skip it: continue.
stops > k and not stops >= k?→
stops counts flights. With exactly k flights we've made k − 1 stops, so one more flight lands somewhere with k stops made in between, which is allowed. Only at k + 1 flights must we stop. That's the "k + 1 is allowed" point she makes at the start.Rule 3, the new one: keep a pricier arrival if it used fewer stops
This is the heart of the problem. Let's run plain Dijkstra's push rule, "push only if new_cost < dist[nei]", on her picture (this is her first board run):
| pop | popped (cost, node, stops) | pushes with the "only if cheaper" rule | dist[0..4] after |
|---|---|---|---|
| 1 | (0, 0, 0) | (100, 1, 1), (400, 2, 1) | [0, 100, 400, ∞, ∞] |
| 2 | (100, 1, 1) | (300, 2, 2): 300 < 400 | [0, 100, 300, ∞, ∞] |
| 3 | (300, 2, 2) | (800, 3, 3), (400, 4, 3) | [0, 100, 300, 800, 400] |
| 4 | (400, 2, 1) | to 3: 900 < 800? no · to 4: 500 < 400? no → thrown away | same |
| 5 | (400, 4, 3) | 3 flights used, not dst → can't fly on | same |
| 6 | (800, 3, 3) | dst → return 800 |
The arrival at node 4 with cost 500 but only 2 flights was thrown away because dist[4] already held 400. But that 400 came with 3 flights and is stuck: it can't fly to 3. The 500 one could have flown on to 3 for a total of 600 with 2 stops. Wrong answer.
Her fix (the "change one line" part of the video): even if the new cost is not smaller, still push it if this route has stops left to continue. "Greater now" doesn't mean "useless later".
if new_cost < dist[nei] or stops < k:
push (new_cost, nei, stops + 1)
dist[nei] = min(dist[nei], new_cost)
stops < k in that condition?→ The neighbour would be reached with
stops + 1 flights. If that's ≤ k, the neighbour can still fly on, so this route might lead somewhere useful even though it's pricier: push it. If stops == k, the neighbour is reached with k + 1 flights and can't fly further. Then it only matters if it is dst and cheaper than what dst already has, which the new_cost < dist[nei] half checks.dist[nei] = min(...) and not just = new_cost?→ Because now we can enter this block with a cost that is not smaller (through the
or). Writing it directly could raise dist from 400 back up to 500. She spots this while coding and switches to min.So why exactly does plain Dijkstra fail here?
Dijkstra's rule "once I know a cheaper way to X, every pricier way to X is useless" is true when cost is the only thing that matters. Here a route has two numbers, cost and stops used. A route can be worse in cost but better in stops, and then neither one beats the other.
| arrival at node 4 | cost | flights used | can still fly to 3? |
|---|---|---|---|
| via 0 → 1 → 2 → 4 | 400 (cheaper) | 3 | no (k = 2 allows 3 flights in total) |
| via 0 → 2 → 4 | 500 (pricier) | 2 | yes → 600 at node 3 |
And if you ignore stops completely, plain Dijkstra returns 500 (route 0→1→2→4→3), which uses 3 stops and isn't allowed. So plain Dijkstra fails in both directions: without a stop check it breaks the limit, and with a stop check but "cheapest only" pruning it gives up on routes it needed.
import heapq
def dijkstra_cost_only(n, flights, src, dst, k):
graph = [[] for _ in range(n)]
for u, v, price in flights:
graph[u].append((v, price))
dist = [float('inf')] * n
dist[src] = 0
pq = [(0, src, 0)]
while pq:
cost, node, stops = heapq.heappop(pq)
if node == dst:
return cost
if cost > dist[node] or stops > k:
continue
for nei, price in graph[node]:
if cost + price < dist[nei]: # BUG: pricier-but-fewer-stops routes are lost
dist[nei] = cost + price
heapq.heappush(pq, (cost + price, nei, stops + 1))
return -1
her = [[0, 1, 100], [0, 2, 400], [1, 2, 200], [2, 3, 500], [2, 4, 100], [4, 3, 100]]
print(dijkstra_cost_only(5, her, 0, 3, 2)) # 800 (wrong: the answer is 600)5Approach steps
- Build a directed adjacency list.
dist = [inf] * n,dist[src] = 0, heap =[(0, src, 0)].- Pop the cheapest
(cost, node, stops). - If node is dst → return cost.
- If
stops > k→ continue (no flights left). - For each flight
(nei, price):new_cost = cost + price. Ifnew_cost < dist[nei]orstops < k: push(new_cost, nei, stops + 1)and setdist[nei] = min(dist[nei], new_cost). - Heap empty and dst never popped → return −1.
6Code (Python)
import heapq
class Solution:
def findCheapestPrice(self, n, flights, src, dst, k):
graph = [[] for _ in range(n)]
for u, v, price in flights:
graph[u].append((v, price)) # directed
dist = [float('inf')] * n
dist[src] = 0
pq = [(0, src, 0)] # (cost, node, flights used)
while pq:
cost, node, stops = heapq.heappop(pq)
if node == dst: # first pop of dst = cheapest allowed
return cost
if stops > k: # no flights left from here
continue
for nei, price in graph[node]:
new_cost = cost + price
if new_cost < dist[nei] or stops < k: # cheaper, OR still has stops to spare
heapq.heappush(pq, (new_cost, nei, stops + 1))
dist[nei] = min(dist[nei], new_cost)
return -17Code line by line
| line | what it means |
|---|---|
| graph[u].append((v, price)) | Flights are one way, so only u's list gets it. |
| pq = [(0, src, 0)] | At src: paid 0, taken 0 flights. Cost first, so the heap sorts by price. |
| cost, node, stops = heapq.heappop(pq) | The cheapest route found so far that still waits to be explored. |
| if node == dst: return cost | Every route in the heap is allowed, and they come out cheapest first, so this is the answer. |
| if stops > k: continue | k + 1 flights already used: landing anywhere else would add a stop too many. |
| new_cost = cost + price | Price of the route extended by this flight. |
| if new_cost < dist[nei] or stops < k: | Keep the route if it's the cheapest so far to nei, or if it will still have flights left after landing. This or is the one line that differs from Dijkstra. |
| heappush(pq, (new_cost, nei, stops + 1)) | One more flight taken. |
| dist[nei] = min(dist[nei], new_cost) | dist keeps the cheapest cost seen; never let a pricier route raise it. |
| return -1 | dst was never popped: no route within k stops. |
8Dry run: heap and dist after every pop
Her picture, src 0, dst 3, k = 2. Heap entries are (cost, node, stops), shown in sorted order. dist = [node0 … node4].
| pop | popped | flights checked | heap AFTER | dist AFTER |
|---|---|---|---|---|
| 1 | (0, 0, 0) | →1: 100 < ∞ ✓ · →2: 400 < ∞ ✓ | (100,1,1) (400,2,1) | [0, 100, 400, ∞, ∞] |
| 2 | (100, 1, 1) | →2: 300 < 400 ✓ | (300,2,2) (400,2,1) | [0, 100, 300, ∞, ∞] |
| 3 | (300, 2, 2) | →3: 800 < ∞ ✓ · →4: 400 < ∞ ✓ | (400,2,1) (400,4,3) (800,3,3) | [0, 100, 300, 800, 400] |
| 4 | (400, 2, 1) | →3: 900, not cheaper, but stops 1 < 2 ✓ push →4: 500, not cheaper, but stops 1 < 2 ✓ push | (400,4,3) (500,4,2) (800,3,3) (900,3,2) | [0, 100, 300, 800, 400] (min keeps 800, 400) |
| 5 | (400, 4, 3) | 3 > k → no flights left, skip | (500,4,2) (800,3,3) (900,3,2) | same |
| 6 | (500, 4, 2) | →3: 600 < 800 ✓ | (600,3,3) (800,3,3) (900,3,2) | [0, 100, 300, 600, 400] |
| 7 | (600, 3, 3) | node 3 = dst → return 600 |
Compare pop 4 with the wrong board run in step 4: the or stops < k keeps (500, 4, 2) alive, and that's the entry that finds 600.
(400, 4, 3) cheap, but stuck (500, 4, 2) pricier, can still fly
(0) —400—→ (2) —100—→ (4) —100—→ (3) flights 3, stops 2, total 600
9Complexity & why LeetCode said "Memory Limit Exceeded"
- Her count: one node can enter the heap once per possible number of stops, so up to about k × E entries, each push/pop costing log. Time about O(kE · log(kE)), space O(kE). With k ≈ 100 and E ≈ 5000 that's about 5·105 entries.
- The cause, in her words: the
or stops < kpushes routes even when they are not cheaper, so the heap fills up with entries. On LeetCode this code got Memory Limit Exceeded. The logic isn't wrong; the constraints are what make it fail.
→ It's actually worse. While
stops < k, the condition pushes every route, even a second route to the same node with the same stop count. So the heap can hold one entry per path of up to k flights, and on dense graphs the number of paths grows like (number of neighbours)k. That's why the memory blows up.→ Yes: remember, for each node, the fewest flights it has been expanded with. The heap pops cheapest first, so if a node comes out again with no fewer flights than before, it is worse in both cost and stops: skip it. Each node is then expanded at most k + 2 times, so the heap gets at most about (k + 2) × E entries.
import heapq
def cheapest_heap_pruned(n, flights, src, dst, k):
graph = [[] for _ in range(n)]
for u, v, price in flights:
graph[u].append((v, price))
fewest = [float('inf')] * n # fewest flights a node was expanded with
pq = [(0, src, 0)]
while pq:
cost, node, stops = heapq.heappop(pq)
if node == dst:
return cost
if stops > k or stops >= fewest[node]: # out of flights, or cheaper AND not-more-stops route already used
continue
fewest[node] = stops
for nei, price in graph[node]:
heapq.heappush(pq, (cost + price, nei, stops + 1))
return -1
her = [[0, 1, 100], [0, 2, 400], [1, 2, 200], [2, 3, 500], [2, 4, 100], [4, 3, 100]]
print(cheapest_heap_pruned(5, her, 0, 3, 2)) # 600stops > k. Push when cheaper OR stops < k, because a pricier route with stops to spare can still win. Correct, but heavy: MLE on LeetCode.Part B · Plain BFS queue (her accepted solution)
1The question
Same question. The idea: we don't really need the heap. The real limit here is the number of flights, and a plain BFS queue already handles routes in order of flights: first everything 1 flight away, then 2 flights, and so on. So explore all allowed routes and read dist[dst] at the end.
2What the constraints tell us
k × E ≈ 5·105 → an O(k × E) method with no log and no pile-up of entries fits easily in both time and memory.
3Intuition
Think of BFS levels as "number of flights taken". Level 1 = cities 1 flight from src, level 2 = 2 flights, …, and we stop expanding after level k + 1. Along the way, dist[x] keeps the cheapest price seen for x. Because the queue hands out routes in order of flights used, any route that arrives later has used at least as many flights as earlier ones. So if it's not cheaper either, it's worse in both ways and safe to drop. That's why "push only if cheaper" is correct again here, while it wasn't in the heap version (the heap hands routes out by cost, so a fewer-flights route can arrive later).
4What changes from Part A (her edits on screen)
- The heap becomes a normal queue (
deque, first in, first out). - Remove "if node == dst: return cost". With a normal queue the first arrival at dst is the one with the fewest flights, not the cheapest. A later route with more flights may be cheaper (LeetCode example below). So we explore everything allowed and read
dist[dst]at the end. - Keep "if stops > k: continue". We still must not expand routes that are out of flights.
- Remove the
or stops < k. Push only ifnew_cost < dist[nei], and then setdist[nei] = new_costdirectly (it's smaller, so no min needed). - At the end:
dist[dst]still infinity → −1, elsedist[dst].
dist[dst] guaranteed to use at most k stops?→ We only push from entries with
stops ≤ k, so every route that ever writes into dist has at most k + 1 flights, which means at most k stops. dist never holds a too-long route.→ Not in BFS. The 2-flight route to 4 (cost 500) comes out of the queue before any 3-flight route, because the queue is in order of flights. So 500 is written first; the 3-flight 400 comes later, and it's cheaper, so it is also kept. Both get explored. In the heap version, 400 came out first just because it was cheaper, and that's what blocked 500. Watch pop 3 and pop 4 in the dry run.
→ Her heap version behaves like Dijkstra, not like DFS, and her final version is BFS. A plain DFS would have to try every path up to k + 1 flights, which is exponential. BFS's natural "level = flights" order is what makes this problem easy.
5Approach steps
- Build the directed adjacency list.
dist = [inf] * n,dist[src] = 0, queue =[(0, src, 0)].- Pop from the front:
(cost, node, stops). Ifstops > k→ continue. - For every flight
(nei, price): ifcost + price < dist[nei]→ update dist[nei] and append(cost + price, nei, stops + 1). - Return −1 if
dist[dst]is infinity, elsedist[dst].
6Code (Python)
from collections import deque
class Solution:
def findCheapestPrice(self, n, flights, src, dst, k):
graph = [[] for _ in range(n)]
for u, v, price in flights:
graph[u].append((v, price))
dist = [float('inf')] * n
dist[src] = 0
queue = deque([(0, src, 0)]) # (cost, node, flights used)
while queue:
cost, node, stops = queue.popleft()
if stops > k: # out of flights
continue
for nei, price in graph[node]:
new_cost = cost + price
if new_cost < dist[nei]: # cheaper than anything seen
dist[nei] = new_cost
queue.append((new_cost, nei, stops + 1))
return -1 if dist[dst] == float('inf') else dist[dst]7Code line by line
| line | what it means |
|---|---|
| queue = deque([(0, src, 0)]) | Normal FIFO queue. Entries come out in order of flights used. |
| cost, node, stops = queue.popleft() | Oldest entry first, not cheapest. No early return. |
| if stops > k: continue | k + 1 flights already: can't fly on. |
| if new_cost < dist[nei]: | Only a cheaper arrival is worth exploring; a pricier one that came later also has no fewer flights, so it's worse in both ways. |
| dist[nei] = new_cost | Cheapest price to nei so far (always within the stop limit). |
| queue.append((new_cost, nei, stops + 1)) | Explore onward from nei later. |
| return -1 if dist[dst] == float('inf') else dist[dst] | Never reached in time → −1. |
8Dry run: queue and dist after every pop
Her picture (src 0, dst 3, k = 2)
| pop | popped (cost, node, stops) | flights checked | queue AFTER (front first) | dist AFTER |
|---|---|---|---|---|
| 1 | (0, 0, 0) | →1: 100 ✓ · →2: 400 ✓ | (100,1,1) (400,2,1) | [0, 100, 400, ∞, ∞] |
| 2 | (100, 1, 1) | →2: 300 < 400 ✓ | (400,2,1) (300,2,2) | [0, 100, 300, ∞, ∞] |
| 3 | (400, 2, 1) | →3: 900 ✓ · →4: 500 ✓ | (300,2,2) (900,3,2) (500,4,2) | [0, 100, 300, 900, 500] |
| 4 | (300, 2, 2) | →3: 800 < 900 ✓ · →4: 400 < 500 ✓ | (900,3,2) (500,4,2) (800,3,3) (400,4,3) | [0, 100, 300, 800, 400] |
| 5 | (900, 3, 2) | node 3 has no flights out | (500,4,2) (800,3,3) (400,4,3) | same |
| 6 | (500, 4, 2) | →3: 600 < 800 ✓ | (800,3,3) (400,4,3) (600,3,3) | [0, 100, 300, 600, 400] |
| 7–9 | (800,3,3) (400,4,3) (600,3,3) | each has stops 3 > 2 → skip | empty | same |
dist[3] = 600 ✓. The 2-flight arrival at node 4 (500, pop 3) was written before the cheaper 3-flight one (400, pop 4), so it wasn't blocked.
LeetCode example 1 (why we can't return at the first dst arrival)
flights: 0→1 (100), 1→2 (100), 2→0 (100), 1→3 (600), 2→3 (200) src 0, dst 3, k = 1 (at most 2 flights)
- pop (0,0,0) → 1 gets 100. queue: (100,1,1).
- pop (100,1,1) → 2 gets 200, 3 gets 700. queue: (200,2,2) (700,3,2).
- pop (200,2,2) → stops 2 > 1, skip. (Its route 0→1→2→3 = 400 would use 2 stops.)
- pop (700,3,2) → skip. Queue empty. dist[3] = 700 ✓.
Here the first arrival happens to be the answer, but in her picture the first arrival at 3 was 900 (pop 3) and the answer turned out to be 600 (pop 6). That's why the BFS version must not return early.
9Complexity & remember
- Time about O(k × E): there are k + 1 levels, and in each level the flights get scanned. No log, because there's no heap.
- Space about O(k × E) in the worst case for the queue (usually much less), plus O(V + E) for the graph.
- Her trade-off: the heap version can finish early (best case, dst is popped almost at once) but pays a log and piles up entries. The BFS version always explores every allowed route, but each step is cheap. With these constraints BFS wins and is accepted.
To be exact: in an unlucky order a node can be improved and queued more than once in the same level, so k × E is a typical figure rather than a hard cap. Part C gives a version where (k + 1) × E is guaranteed.
stops > k · push only if cheaper · no early return · answer is dist[dst] (or −1).Her closing lesson: here plain BFS was enough. In other problems (like the next one) the heap is really needed. Decide from the problem.
Part C · Bellman-Ford style rounds (extra)
Not in the video, but it's the classic answer for "shortest path with at most k + 1 edges", and it makes the BFS idea completely strict.
1Idea
Keep dist = "cheapest price using at most r flights". Start with r = 0: only src is 0. Each round allows one more flight: for every flight u → v, try dist[u] + price for v. After k + 1 rounds, dist[dst] is the cheapest price with at most k + 1 flights.
2The one trap: copy the array each round
Read from the old round and write into a copy. If you updated in place, a round could use a value written earlier in the same round and chain two flights in one round, breaking the count.
→ Flights 0→1 (1), 1→2 (1), k = 0 (only 1 flight). Round 1 in place: 0→1 makes dist[1] = 1, then 1→2 reads that fresh 1 and makes dist[2] = 2, a 2-flight route in a 1-flight round. With a copy, round 1 reads the old dist[1] = ∞, so dist[2] stays ∞ ✓.
3Steps
dist = [inf] * n,dist[src] = 0.- Repeat k + 1 times:
new = dist[:]; for each flight u → v: ifdist[u] + price < new[v], setnew[v]. Thendist = new. - Return −1 if
dist[dst]is infinity, elsedist[dst].
4Code (Python)
class Solution:
def findCheapestPrice(self, n, flights, src, dst, k):
dist = [float('inf')] * n
dist[src] = 0
for _ in range(k + 1): # one round = one more flight allowed
new = dist[:] # read old, write copy
for u, v, price in flights:
if dist[u] + price < new[v]:
new[v] = dist[u] + price
dist = new
return -1 if dist[dst] == float('inf') else dist[dst]5Dry run on her picture (k = 2 → 3 rounds)
| after round | meaning | dist[0..4] |
|---|---|---|
| 0 | 0 flights | [0, ∞, ∞, ∞, ∞] |
| 1 | ≤ 1 flight | [0, 100, 400, ∞, ∞] |
| 2 | ≤ 2 flights | [0, 100, 300, 900, 500] |
| 3 | ≤ 3 flights | [0, 100, 300, 600, 400] |
Round 3 computes dist[3] from round 2's values: min(900, 300 + 500, 500 + 100) = 600. It reads the 2-flight 500 for node 4, not round 3's fresh 400 (which used 3 flights). That's the copy at work.
6Complexity
Time O((k + 1) × E) exactly, space O(n). Short, strict, and no graph building needed.
Part D · Revision page
| A: heap (cost, node, stops) | B: BFS queue | C: k + 1 rounds | |
|---|---|---|---|
| order of work | cheapest first | fewest flights first | round by round |
| push / update when | cheaper or stops < k | cheaper | cheaper than in the copy |
| early return | yes, when dst is popped | no, read dist[dst] at the end | no |
| stops check | stops > k → skip | stops > k → skip | loop runs k + 1 times |
| cost | heavy; MLE on LeetCode | ≈ O(kE), accepted | O((k + 1)E), O(n) space |
| plain Dijkstra on cost only… | result on her picture (k = 2) |
|---|---|
| …ignoring stops | 500 (route with 3 stops, not allowed) |
| …with a stops check, but "push only if cheaper" | 800 (the 600 route was thrown away at node 4) |
| correct answer | 600 (0 → 2 → 4 → 3) |
2. Pop with flights > k (not dst) → can't fly on.
3. Cheaper isn't everything: a pricier route with fewer stops can still win, so Dijkstra's "cheaper only" rule fails.
4. Heap version: push if cheaper OR stops < k; return when dst is popped (heavy).
5. BFS version: queue goes by flights, so "push if cheaper" is safe again; answer = dist[dst] at the end.
✗ using plain Dijkstra pruning with the heap (loses fewer-stop routes)
✗ returning early at dst in the BFS version (first arrival = fewest flights, not cheapest)
✗
dist[nei] = new_cost in the heap version (must be min)✗ updating in place in the rounds version (forgetting the copy)
✗ hard-coding 0 instead of src (she did this once on screen)
s = Solution() her = [[0, 1, 100], [0, 2, 400], [1, 2, 200], [2, 3, 500], [2, 4, 100], [4, 3, 100]] print(s.findCheapestPrice(5, her, 0, 3, 2)) # 600 print(s.findCheapestPrice(5, her, 0, 3, 3)) # 500 print(s.findCheapestPrice(4, [[0, 1, 100], [1, 2, 100], [2, 0, 100], [1, 3, 600], [2, 3, 200]], 0, 3, 1)) # 700 print(s.findCheapestPrice(3, [[0, 1, 100], [1, 2, 100], [0, 2, 500]], 0, 2, 1)) # 200 print(s.findCheapestPrice(3, [[0, 1, 100], [1, 2, 100], [0, 2, 500]], 0, 2, 0)) # 500 print(s.findCheapestPrice(3, [[0, 1, 100]], 0, 2, 1)) # -1
Based on this video: Cheapest Flights Within K Stops