You are given a network of n directed nodes, labeled from 1 to n. You are also given times, a list of directed edges where times[i] = (ui, vi, ti).
ui is the source node (an integer from 1 to n)vi is the target node (an integer from 1 to n)ti is the time it takes for a signal to travel from the source to the target node (an integer greater than or equal to 0).You are also given an integer k, representing the node that we will send a signal from.
Return the minimum time it takes for all of the n nodes to receive the signal. If it is impossible for all the nodes to receive the signal, return -1 instead.
Example 1:
Input: times = [[1,2,1],[2,3,1],[1,4,4],[3,4,1]], n = 4, k = 1
Output: 3Example 2:
Input: times = [[1,2,1],[2,3,1]], n = 3, k = 2
Output: -1Constraints:
1 <= k <= n <= 1001 <= times.length <= 1000
You should aim for a solution as good or better than O(ElogV) time and O(V + E) space, where E is the number of edges and V is the number of vertices (nodes).
As we are given the source node and we need to find the minimum time to reach all nodes, this represents the shortest path from the source node to all nodes. Can you think of a standard algorithm to find the shortest path from a source to a destination? Maybe a heap-based algorithm is helpful.
We can use Dijkstra's algorithm to find the shortest path from a source to destination. We end up finding the shortest paths from the source to the nodes that we encounter in finding the destination. So, to find shortest path for all nodes from the source, we need to perform Dijkstra's algorithm until the heap in this algorithm becomes empty. How would you implement this?
We use a Min-Heap as we need to find the minimum time. We create an adjacency list for the given times (weighted edges). We also initialize an array dist[] of size n (number of nodes) which represents the distance from the source to all nodes, initialized with infinity. We put dist[source] = 0. Then we continue the algorithm. After the heap becomes empty, if we don't visit any node, we return -1; otherwise, we return the time.
Before attempting this problem, you should be comfortable with:
We want to know how long it takes for a signal to reach all nodes starting from node k.
Using DFS, we try all possible paths from k and keep track of the minimum time needed to reach each node.
After exploring all reachable paths:
-1.times where each edge has a weight.∞ for all nodes.dfs from node k with time 0.dfs:dfs to neighbors.dfs:∞, return -1, else return the maximum.class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
adj = defaultdict(list)
for u, v, w in times:
adj[u].append((v, w))
dist = {node: float("inf") for node in range(1, n + 1)}
def dfs(node, time):
if time >= dist[node]:
return
dist[node] = time
for nei, w in adj[node]:
dfs(nei, time + w)
dfs(k, 0)
res = max(dist.values())
return res if res < float('inf') else -1Where is the number of vertices and is the number of edges.
We want the shortest time between every pair of nodes so that we can easily know how long it takes for the signal to reach all nodes starting from k.
Floyd–Warshall is an all-pairs shortest path algorithm:
(i, j) by allowing an intermediate node mid.dist[i][j] stores the shortest time from i to j.Once all shortest paths are known:
k.-1.dist matrix initialized with ∞.dist[u][v] = w for every directed edge (u → v) with weight w.dist[i][i] = 0 for all nodes.mid:(i, j), updatedist[i][j] = min(dist[i][j], dist[i][mid] + dist[mid][j])k to all nodes.∞, return -1; otherwise return it.class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
inf = float('inf')
dist = [[inf] * n for _ in range(n)]
for u, v, w in times:
dist[u-1][v-1] = w
for i in range(n):
dist[i][i] = 0
for mid in range(n):
for i in range(n):
for j in range(n):
dist[i][j] = min(dist[i][j], dist[i][mid] + dist[mid][j])
res = max(dist[k-1])
return res if res < inf else -1Where is the number of vertices.
We want the shortest time for a signal to reach every node starting from node k.
Bellman–Ford works by:
n - 1 rounds, all shortest paths are guaranteed to be found (because the longest simple path has at most n - 1 edges).Once distances are finalized:
∞), return -1.dist of size n with ∞.dist[k - 1] = 0 (source node).n - 1 times:(u → v, w):dist[u] + w < dist[v], update dist[v].dist.∞, return -1; otherwise return it.class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
dist = [float('inf')] * n
dist[k - 1] = 0
for _ in range(n - 1):
for u, v, w in times:
if dist[u - 1] + w < dist[v - 1]:
dist[v - 1] = dist[u - 1] + w
max_dist = max(dist)
return max_dist if max_dist < float('inf') else -1Where is the number of vertices and is the number of edges.
SPFA (Shortest Path Faster Algorithm) is an optimized version of Bellman–Ford.
Instead of relaxing all edges every time, we:
Whenever a node’s shortest time decreases, its neighbors might also get a shorter path — so we push that node into the queue.
This avoids unnecessary work and is usually much faster in practice.
times.dist for all nodes as ∞.dist[k] = 0 and push (k, 0) into a queue.(node, time)time > dist[node], skip it (outdated path)time + weight < dist[neighbor]:dist[neighbor](neighbor, newTime) into the queuedist.∞), return -1; otherwise return the max time.class Solution:
def networkDelayTime(self, times, n, k):
adj = defaultdict(list)
for u, v, w in times:
adj[u].append((v, w))
dist = {node: float("inf") for node in range(1, n + 1)}
q = deque([(k, 0)])
dist[k] = 0
while q:
node, time = q.popleft()
if dist[node] < time:
continue
for nei, w in adj[node]:
if time + w < dist[nei]:
dist[nei] = time + w
q.append((nei, time + w))
res = max(dist.values())
return res if res < float('inf') else -1Where is the number of vertices and is the number of edges.
Dijkstra's Algorithm finds the shortest time from the source node k to all other nodes when all edge weights are non-negative.
The key idea:
By doing this, we gradually spread the signal in increasing order of time.
times.(0, k) (time, node).visited set to avoid reprocessing nodes.visited, skip.visited and update the current time.n nodes are visited, return the maximum time used.-1 (some nodes are unreachable).class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
edges = collections.defaultdict(list)
for u, v, w in times:
edges[u].append((v, w))
minHeap = [(0, k)]
visit = set()
t = 0
while minHeap:
w1, n1 = heapq.heappop(minHeap)
if n1 in visit:
continue
visit.add(n1)
t = w1
for n2, w2 in edges[n1]:
if n2 not in visit:
heapq.heappush(minHeap, (w1 + w2, n2))
return t if len(visit) == n else -1Where is the number of vertices and is the number of edges.
The answer is the maximum time to reach any node, but only if all nodes are reachable. Some solutions return the maximum distance without first checking if any node remains at infinity. This returns an incorrect large value instead of -1 when nodes are unreachable.
Nodes are numbered from 1 to n, not 0 to n-1. Mixing up indexing when building the adjacency list or distance array causes out-of-bounds errors or skipped nodes. Consistently use either 1-based arrays of size n+1 or subtract 1 from all node numbers.
In Dijkstra's algorithm, once a node is finalized (popped from the min-heap), its shortest distance is determined. Processing the same node again wastes time and can cause issues in some implementations. Always skip nodes that have already been visited or whose current distance exceeds the known shortest.