Network Delay Time
A signal starts at one node and travels along directed edges that each take some time. Find how long until every node has received it, or report that some node never does.
The signal starts at 1. Every other node's best known time is still infinity.
1import heapq2from collections import defaultdict3 4def network_delay_time(times, n, k):5 graph = defaultdict(list)6 for u, v, w in times:7 graph[u].append((v, w))8 9 dist = {}10 heap = [(0, k)]11 while heap:12 d, node = heapq.heappop(heap)13 if node in dist:14 continue15 dist[node] = d16 for nei, w in graph[node]:17 if nei not in dist:18 heapq.heappush(heap, (d + w, nei))19 20 return max(dist.values()) if len(dist) == n else -1Read the 13 steps as text
- 1The signal starts at 1. Every other node's best known time is still infinity.
- 21 is the closest unfinished node at 0. Nothing found later can beat that, so settle it.
- 3Reach 2 via 1 in 0 + 2 = 2. First route found.
- 4Reach 3 via 1 in 0 + 5 = 5. First route found.
- 52 is the closest unfinished node at 2. Nothing found later can beat that, so settle it.
- 6Reach 3 in 3, beating the 5 already queued. The old entry stays but is now stale.
- 7Reach 4 via 2 in 2 + 4 = 6. First route found.
- 83 is the closest unfinished node at 3. Nothing found later can beat that, so settle it.
- 9Reach 4 in 5, beating the 6 already queued. The old entry stays but is now stale.
- 103 was already settled โ this is a stale heap entry. Discard it.
- 114 is the closest unfinished node at 5. Nothing found later can beat that, so settle it.
- 124 was already settled โ this is a stale heap entry. Discard it.
- 13All 4 nodes settled. The slowest is 5, so that's when the whole network has the signal.
The idea
The phrasing hides a standard problem. 'When has everyone received it' is the *slowest* of the shortest delivery times โ so this is single-source shortest paths, and then a max over the results. Recognising that is most of the work.
Edge weights are positive, which is exactly the condition Dijkstra needs. Plain BFS would be wrong here: BFS finds the path with the fewest edges, and the fewest edges is not the least time. In the sample below, 1โ3 is one edge costing 5, while 1โ2โ3 is two edges costing 3.
Dijkstra's insight is that if you always expand the closest unfinished node, then the moment you pop a node its distance is final. Nothing discovered later can improve it, because every other route out of the frontier is already at least as long and edges only add. That's why the algorithm can settle a node permanently instead of revisiting it.
The heap is what makes 'closest unfinished node' cheap. The `if node in dist: continue` line is the other half โ a node can be pushed several times with different tentative distances, and this discards the stale copies without ever needing to delete from the heap.
The approach
- 1Build an adjacency list from the edge list.
- 2Push (0, source) onto a min-heap keyed by distance, and keep a dict of settled distances.
- 3Pop the smallest. If that node is already settled, discard it and continue โ it's a stale entry.
- 4Otherwise settle it at that distance, and push (distance + weight, neighbour) for each unsettled neighbour.
- 5When the heap empties, every reachable node is settled. If any node is missing, return -1; otherwise return the largest settled distance.
Complexity
Each edge can push at most one heap entry, and each push or pop costs log of the heap size. The space is the adjacency list plus the heap.
Code
import heapq
from collections import defaultdict
def network_delay_time(times, n, k):
graph = defaultdict(list)
for u, v, w in times:
graph[u].append((v, w))
dist = {}
heap = [(0, k)]
while heap:
d, node = heapq.heappop(heap)
if node in dist:
continue
dist[node] = d
for nei, w in graph[node]:
if nei not in dist:
heapq.heappush(heap, (d + w, nei))
return max(dist.values()) if len(dist) == n else -1What goes wrong
- โขUsing BFS because the weights look small. It finds fewest-edges, not least-total-weight, and quietly returns a wrong answer on any graph where a longer route is cheaper.
- โขForgetting the stale-entry check. Without it a node gets settled more than once, and the second, larger distance overwrites the correct one.
- โขReturning the sum of the distances, or the distance to the last node popped. The answer is the maximum over all of them โ the signal isn't done until the slowest node has it.
- โขMissing the unreachable case. If fewer than n nodes end up settled the answer is -1, and checking `len(dist) == n` is easier to get right than tracking reachability separately.
- โขReaching for Dijkstra reflexively on a graph with negative weights. The settle-once argument collapses there, and the answer becomes Bellman-Ford.