Find the minimum time for a signal to reach all nodes in a directed, weighted graph. Run Dijkstra's algorithm from the source with a min-heap; the answer is the largest finalized distance, or -1 if any node stays unreachable.
You have n network nodes (labeled 1 to n) and directed, weighted edges in times[i] = [u, v, w] (signal travels from u to v in w time units). A signal starts at node k. Return the minimum time for all nodes to receive the signal, or -1 if any node is unreachable.
Worked example. times=[[2,1,1],[2,3,1],[3,4,1]], n=4, k=2. Starting from node 2: node 1 costs 1, node 3 costs 1, node 4 costs 2. The last node to receive the signal is node 4 at time 2. Answer: 2.
min-heap of (distance, node) pairs delivers the nearest unfinalized node in O(log n). The answer is max(dist.values()) — the last node that the signal reaches — or -1 if fewer than n nodes were finalized.(dst, weight) neighbors for every source node. This makes edge lookups O(degree).(0, k) — distance zero at the starting node. Keep a dist map (node → finalized distance).(d, u) pair with the smallest d. If u is already finalized, skip it — a stale entry from a prior relaxation.dist[u] = d. This distance is optimal and will never decrease.v, push (d + weight, v) onto the heap.dist.size < n, return -1 (some node unreachable). Otherwise return Math.max(...dist.values()).if (dist.has(u)) continue). Beginners sometimes use a separate "visited set" outside the heap check, or forget to skip duplicates, leading to incorrect double-relaxations.The adjacency list is O(V + E). The heap holds at most O(E) entries (one per relaxation). The dist map is O(V). A Fibonacci heap could achieve O(V log V + E) but is rarely used in practice.
2, 4 nodes total. Heap seeded with (0, 2). All distances start at ∞.dist map is empty; finalized nodes will be added to it.1function networkDelayTime(times: number[][], n: number, k: number): number {2 // Build adjacency list: src -> [(dst, weight)]3▶ const adj = new Map<number, [number, number][]>();4▶ for (let i = 1; i <= n; i++) adj.set(i, []);5▶ for (const [u, v, w] of times) {6▶ adj.get(u)!.push([v, w]);7 }89 // Min-heap ordered by (dist, node). Encode as [dist, node].10▶ const dist = new Map<number, number>();11 // Simple min-heap via sorted array (replace with binary heap for production)12▶ const heap: [number, number][] = [[0, k]];1314 while (heap.length > 0) {15 heap.sort((a, b) => a[0] - b[0]);16 const [d, u] = heap.shift()!;17 if (dist.has(u)) continue; // already finalized18 dist.set(u, d);19 for (const [v, w] of (adj.get(u) ?? [])) {20 if (!dist.has(v)) {21 heap.push([d + w, v]);22 }23 }24 }2526 if (dist.size !== n) return -1;27 return Math.max(...dist.values());28}
function networkDelayTime(times: number[][], n: number, k: number): number {
// Build adjacency list: src -> [(dst, weight)]
const adj = new Map<number, [number, number][]>();
for (let i = 1; i <= n; i++) adj.set(i, []);
for (const [u, v, w] of times) {
adj.get(u)!.push([v, w]);
}
// Min-heap ordered by (dist, node). Encode as [dist, node].
const dist = new Map<number, number>();
// Simple min-heap via sorted array (replace with binary heap for production)
const heap: [number, number][] = [[0, k]];
while (heap.length > 0) {
heap.sort((a, b) => a[0] - b[0]);
const [d, u] = heap.shift()!;
if (dist.has(u)) continue; // already finalized
dist.set(u, d);
for (const [v, w] of (adj.get(u) ?? [])) {
if (!dist.has(v)) {
heap.push([d + w, v]);
}
}
}
if (dist.size !== n) return -1;
return Math.max(...dist.values());
}1..n, then populate it from times. Using a Map keeps lookup clean and avoids off-by-one index bugs with the 1-based node labels.[0, k] — cost zero to reach the source from itself. The distmap will hold finalized shortest distances; it doubles as the "visited" check.dist.has(u) is already true, this is a stale heap entry from an earlier relaxation — skip it. This is the critical correctness guard.dist[u] = d (now optimal). For each unfinalized neighbor v, we push [d + w, v] onto the heap. We only skip already-finalized neighbors to avoid wasted heap entries.n nodes were finalized, some node is unreachable — return -1. Otherwise the answer is Math.max(...dist.values()): the time the last node receives the signal.O(VE)) or SPFA. Negative cycles make the problem undefined.(cost, node, hopsLeft) and use a modified Dijkstra or Bellman-Ford over K iterations.parent map: on each finalization record parent[u] = prevNode, then trace back from the last-reached node.k is finalized; dist.size === 1, which is less than n (for n > 1) → return -1.function networkDelayTime(times: number[][], n: number, k: number): number {
// Build adjacency list: src -> [(dst, weight)]
const adj = new Map<number, [number, number][]>();
for (let i = 1; i <= n; i++) adj.set(i, []);
for (const [u, v, w] of times) {
adj.get(u)!.push([v, w]);
}
// Min-heap ordered by (dist, node). Encode as [dist, node].
const dist = new Map<number, number>();
// Simple min-heap via sorted array (replace with binary heap for production)
const heap: [number, number][] = [[0, k]];
while (heap.length > 0) {
heap.sort((a, b) => a[0] - b[0]);
const [d, u] = heap.shift()!;
if (dist.has(u)) continue; // already finalized
dist.set(u, d);
for (const [v, w] of (adj.get(u) ?? [])) {
if (!dist.has(v)) {
heap.push([d + w, v]);
}
}
}
if (dist.size !== n) return -1;
return Math.max(...dist.values());
}Skip the priority queue entirely: initialise every distance to Infinity, then relax all edges n − 1 times. Simpler to reason about than Dijkstra — just repeatedly apply the rule dist[v] = min(dist[v], dist[u] + w).
function networkDelayTime(times: number[][], n: number, k: number): number {
const dist = new Array<number>(n + 1).fill(Infinity);
dist[k] = 0;
// Relax every edge n-1 times; shortest paths use at most n-1 edges.
for (let iter = 0; iter < n - 1; iter++) {
let changed = false;
for (const [u, v, w] of times) {
if (dist[u] !== Infinity && dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
changed = true;
}
}
if (!changed) break; // early exit once nothing improves
}
let ans = 0;
for (let i = 1; i <= n; i++) {
if (dist[i] === Infinity) return -1; // some node unreachable
ans = Math.max(ans, dist[i]);
}
return ans;
}n − 1times — O(n · E) — versus Dijkstra's O(E log n). On dense graphs the extra passes add up. Dijkstra finalises each node once by always expanding the closest unsettled vertex via a min-heap.| shortest path, non-negative weights | Dijkstra + min-heap |
| "minimum time / cost to reach all nodes" | Dijkstra, return max(dist) |
| weighted directed graph, single source | dist map + heap seeded at (0, src) |
| "return -1 if unreachable" | check dist.size === n after loop |
const adj = new Map<number, [number, number][]>();
for (const [u, v, w] of times) adj.get(u)!.push([v, w]);
const dist = new Map<number, number>();
const heap: [number, number][] = [[0, k]]; // [dist, node]
while (heap.length) {
heap.sort((a, b) => a[0] - b[0]);
const [d, u] = heap.shift()!;
if (dist.has(u)) continue;
dist.set(u, d);
for (const [v, w] of adj.get(u) ?? []) {
if (!dist.has(v)) heap.push([d + w, v]);
}
}
return dist.size !== n ? -1 : Math.max(...dist.values());times=[[2,1,1],[2,3,1],[3,4,1]], n=4, k=2, what does the algorithm return?