Time for a signal to reach every node
A network has n nodes labelled 1 to n. Each entry
[u, v, w] of times is a one-way link: a signal sent from u
reaches v after w units of time. A signal is sent from node
k. Return the time at which every node has received it, or −1 if some node never
does.
Examples
| Input | Output | Why |
|---|---|---|
times = [[2, 1, 1], [2, 3, 1], [3, 4, 1]], n = 4, k = 2 | 2 | Node 4 is reached last, through 3, at time 2. |
times = [[1, 2, 5], [1, 3, 1], [3, 2, 1]], n = 3, k = 1 | 2 | The route through 3 beats the direct link to 2. |
times = [[1, 2, 1]], n = 2, k = 2 | -1 | The only link points the wrong way. |
Constraints
- 1 ≤ n ≤ 100
- 1 ≤ |times| ≤ 6000
- 0 ≤ w ≤ 100
- There may be several links between the same pair of nodes
Hints
Hint 1
The answer is the largest of the shortest travel times from k to every node. So the problem is single-source shortest paths.
Hint 2
Breadth-first search finds shortest paths only when every edge costs the same. With weights, a node discovered early may still be reached sooner by a longer chain of cheaper links.
Hint 3
Dijkstra: keep a heap of (time, node). Pop the smallest; the first time a node is popped, its time is final, because every other route reaches it through a node that is no nearer. Push its neighbours with their tentative times.
Where people lose marks
- Marking a node as done when it is pushed rather than when it is popped. A later, cheaper route to it would then be ignored, which is breadth-first search with weights, and wrong.
- Forgetting parallel links between the same two nodes: an adjacency list keeps all of them, and the heap picks the cheapest automatically.
- Returning the time of the last node popped without checking that every node was reached. Unreached nodes never appear in the heap.
- Indexing nodes from 0 when the input labels them from 1.
Dry run on [[1,2,4],[1,3,2],[3,4,1],[4,2,1],[2,5,3],[4,5,6]],5,1
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 5arrival time, nodes 1..n00∞1∞2∞3∞4heap (time, node)2:304:21settled1
Pop node 1 at time 0: no entry in the heap is smaller, so 0 is final. Push 2 at 4, 3 at 2.
-
Step 2 / 5arrival time, nodes 1..n00∞122∞3∞4heap (time, node)3:404:21settled2
Pop node 3 at time 2: no entry in the heap is smaller, so 2 is final. Push 4 at 3.
-
Step 3 / 5arrival time, nodes 1..n00∞12233∞4heap (time, node)4:204:219:52settled3
Pop node 4 at time 3: no entry in the heap is smaller, so 3 is final. Push 2 at 4, 5 at 9.
-
Step 4 / 5arrival time, nodes 1..n00412233∞4heap (time, node)4:207:519:52settled4
Pop node 2 at time 4: no entry in the heap is smaller, so 4 is final. Push 5 at 7.
-
Step 5 / 5arrival time, nodes 1..n0041223374heap (time, node)9:50settled5
Pop node 5 at time 7: no entry in the heap is smaller, so 7 is final. It has no unsettled neighbours. Every node is settled; the last receives the signal at 7.
How to think about it
The signal spreads along every link at once, so each node receives it at its
shortest-path distance from k, and every node has it at the largest of those
distances. If any distance is infinite, the answer is −1.
Dijkstra's algorithm computes the distances when weights are non-negative. Keep a priority queue of tentative arrival times:
- Pop the node with the smallest tentative time. If it has already been settled, skip it: an earlier, smaller entry for it was popped before.
- Otherwise its time is final. Any other route to it would have to leave the settled region through some node with a larger or equal time, and non-negative weights can only add to that.
- For each outgoing link
u → vof weightw, pushtime + wforv.
Pushing a new entry instead of decreasing an existing key keeps the code short; the stale entries
are skipped when popped. With E links the heap holds at most E entries, so
the cost is O(E log E).
The same loop finds the cheapest flight with a budget of stops (add the stop count to the state), the path with the least effort on a grid (edge weight = height difference), and the most reliable route (multiply probabilities, or add their negative logarithms). Negative weights break it; Bellman–Ford handles those in O(VE).
Complexity. Time O(E log E) · Space O(V + E)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
import heapq
def network_delay(times, n, k):
adj = [[] for _ in range(n + 1)]
for u, v, w in times:
adj[u].append((v, w))
dist = {} # node -> final arrival time
heap = [(0, k)]
while heap:
t, u = heapq.heappop(heap)
if u in dist:
continue # a stale entry: u was settled earlier
dist[u] = t
for v, w in adj[u]:
if v not in dist:
heapq.heappush(heap, (t + w, v))
return max(dist.values()) if len(dist) == n else -1#include <vector>
#include <queue>
#include <functional>
#include <algorithm>
int solve(const std::vector<std::vector<int>>& times, int n, int k) {
std::vector<std::vector<std::pair<int, int>>> adj(n + 1);
for (const auto& e : times) adj[e[0]].push_back({e[1], e[2]});
std::vector<int> dist(n + 1, -1); // -1 = not yet settled
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<>> heap;
heap.push({0, k});
while (!heap.empty()) {
auto [t, u] = heap.top();
heap.pop();
if (dist[u] != -1) continue; // a stale entry
dist[u] = t;
for (auto [v, w] : adj[u]) if (dist[v] == -1) heap.push({t + w, v});
}
int worst = 0;
for (int v = 1; v <= n; ++v) {
if (dist[v] == -1) return -1;
worst = std::max(worst, dist[v]);
}
return worst;
}import java.util.*;
class Solution {
public int networkDelay(int[][] times, int n, int k) {
List<List<int[]>> adj = new ArrayList<>();
for (int i = 0; i <= n; i++) adj.add(new ArrayList<>());
for (int[] e : times) adj.get(e[0]).add(new int[]{e[1], e[2]});
int[] dist = new int[n + 1];
Arrays.fill(dist, -1); // -1 = not yet settled
PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
heap.add(new int[]{0, k});
while (!heap.isEmpty()) {
int[] top = heap.poll();
int t = top[0], u = top[1];
if (dist[u] != -1) continue; // a stale entry
dist[u] = t;
for (int[] e : adj.get(u)) if (dist[e[0]] == -1) heap.add(new int[]{t + e[1], e[0]});
}
int worst = 0;
for (int v = 1; v <= n; v++) {
if (dist[v] == -1) return -1;
worst = Math.max(worst, dist[v]);
}
return worst;
}
}function networkDelay(times, n, k) {
const adj = Array.from({ length: n + 1 }, () => []);
for (const [u, v, w] of times) adj[u].push([v, w]);
// a binary heap of [time, node]
const heap = [];
const push = e => { heap.push(e); let i = heap.length - 1; while (i > 0) { const p = (i - 1) >> 1; if (heap[p][0] <= heap[i][0]) break; [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; } };
const pop = () => { const top = heap[0], last = heap.pop(); if (heap.length) { heap[0] = last; let i = 0; for (;;) { const l = 2 * i + 1, r = l + 1; let m = i; if (l < heap.length && heap[l][0] < heap[m][0]) m = l; if (r < heap.length && heap[r][0] < heap[m][0]) m = r; if (m === i) break; [heap[m], heap[i]] = [heap[i], heap[m]]; i = m; } } return top; };
const dist = new Map(); // node -> final arrival time
push([0, k]);
while (heap.length) {
const [t, u] = pop();
if (dist.has(u)) continue; // a stale entry
dist.set(u, t);
for (const [v, w] of adj[u]) if (!dist.has(v)) push([t + w, v]);
}
return dist.size === n ? Math.max(...dist.values()) : -1;
}use std::cmp::Reverse;
use std::collections::BinaryHeap;
fn solve(times: &[[i64; 3]], n: i64, k: i64) -> i64 {
let n = n as usize;
let mut adj: Vec<Vec<(usize, i64)>> = vec![Vec::new(); n + 1];
for e in times {
adj[e[0] as usize].push((e[1] as usize, e[2]));
}
let mut dist: Vec<Option<i64>> = vec![None; n + 1];
let mut heap = BinaryHeap::new();
heap.push(Reverse((0i64, k as usize)));
while let Some(Reverse((t, u))) = heap.pop() {
if dist[u].is_some() {
continue; // a stale entry
}
dist[u] = Some(t);
for &(v, w) in &adj[u] {
if dist[v].is_none() {
heap.push(Reverse((t + w, v)));
}
}
}
let mut worst = 0;
for v in 1..=n {
match dist[v] {
None => return -1,
Some(d) => worst = worst.max(d),
}
}
worst
}import qualified Data.Map.Strict as M
import qualified Data.Set as S
solve :: [[Int]] -> Int -> Int -> Int
solve times n k = go (S.singleton (0, k)) M.empty
where
adj = M.fromListWith (++) [(u, [(v, w)]) | [u, v, w] <- times]
-- the set is the heap: its minimum is the smallest (time, node) entry
go heap dist = case S.minView heap of
Nothing -> if M.size dist == n then maximum (M.elems dist) else -1
Just ((t, u), rest)
| M.member u dist -> go rest dist -- a stale entry
| otherwise ->
let dist' = M.insert u t dist
next = [(t + w, v) | (v, w) <- M.findWithDefault [] u adj, not (M.member v dist')]
in go (foldr S.insert rest next) dist'
Variants to try
- Cheapest flights within K stops: the state is (node, stops used); Dijkstra or a Bellman–Ford limited to K + 1 rounds.
- Dense graphs with V up to a few thousand: the array version of Dijkstra, O(V²), with no heap.
- All pairs of nodes: Floyd–Warshall in O(V³), or Dijkstra from every node.
- Weights of 0 and 1 only: a deque instead of a heap (0-1 BFS), O(V + E).
Next problems
Shortest distances from one node
Return the shortest distance from a source to every node of a weighted directed graph: Dijkstra's algorithm with a heap.
Open the problem → Medium20 minShortest distances with negative edges
Return the shortest distance from a source to every node when weights can be negative, or report a negative cycle the source can reach.
Open the problem → Medium25 minCheapest fare with at most k stops
Find the cheapest route that changes plane at most k times. Plain Dijkstra fails here; k + 1 rounds of Bellman-Ford do not.
Open the problem →Write a solution and run it against the real test table.
Keep going
Shortest distances from one node
Return the shortest distance from a source to every node of a weighted directed graph: Dijkstra's algorithm with a heap.
Open the problem → Medium20 minShortest distances with negative edges
Return the shortest distance from a source to every node when weights can be negative, or report a negative cycle the source can reach.
Open the problem → Medium25 minCheapest fare with at most k stops
Find the cheapest route that changes plane at most k times. Plain Dijkstra fails here; k + 1 rounds of Bellman-Ford do not.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.