743. Network Delay Time - Explanation
Description
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).
uiis the source node (an integer from1ton)viis the target node (an integer from1ton)tiis the time it takes for a signal to travel from the source to the target node (an integer greater than or equal to0).
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
Topics
Recommended Time & Space Complexity
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).
Hint 1
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.
Hint 2
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?
Hint 3
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.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Graph Representation (Adjacency List) - Building and traversing weighted directed graphs
- Depth First Search (DFS) - Recursive graph traversal with path tracking
- Shortest Path Algorithms - Understanding Dijkstra's, Bellman-Ford, and Floyd-Warshall concepts
- Priority Queue / Min-Heap - Efficiently extracting minimum elements for Dijkstra's algorithm
- Hash Maps - Storing distances and graph edges for quick lookups
1. Depth First Search
Intuition
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.
- If we reach a node with a better (smaller) time, we update it.
- If the current path is already worse than a known path, we stop exploring it (pruning).
After exploring all reachable paths:
- The answer is the maximum time among all nodes (last node to receive the signal).
- If any node is unreachable, return
-1.
Algorithm
- Build a graph from
timeswhere each edge has a weight. - Initialize a distance array with
∞for all nodes. - Start
dfsfrom nodekwith time0. - During
dfs:- If current time ≥ known shortest time, stop.
- Otherwise, update the shortest time and continue
dfsto neighbors.
- After
dfs:- Take the maximum distance.
- If any distance is
∞, 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 -1Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of vertices and is the number of edges.
2. Floyd Warshall Algorithm
Intuition
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:
- It repeatedly tries to improve the shortest path between every
(i, j)by allowing an intermediate nodemid. - After processing all intermediates,
dist[i][j]stores the shortest time fromitoj.
Once all shortest paths are known:
- Look at the row corresponding to the starting node
k. - The maximum value in that row is the time when the last node receives the signal.
- If any node is unreachable (distance = ∞), return
-1.
Algorithm
- Create a
distmatrix initialized with∞. - Set
dist[u][v] = wfor every directed edge(u → v)with weightw. - Set
dist[i][i] = 0for all nodes. - For each node
mid:- For every pair
(i, j), updatedist[i][j] = min(dist[i][j], dist[i][mid] + dist[mid][j])
- For every pair
- Take the maximum distance from node
kto all nodes. - If the maximum is
∞, 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 -1Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of vertices.
3. Bellman Ford Algorithm
Intuition
We want the shortest time for a signal to reach every node starting from node k.
Bellman–Ford works by:
- Relaxing (updating) all edges repeatedly.
- Each relaxation tries to improve the shortest distance to a node using one more edge.
- After
n - 1rounds, all shortest paths are guaranteed to be found (because the longest simple path has at mostn - 1edges).
Once distances are finalized:
- The maximum distance tells us when the last node receives the signal.
- If any node is still unreachable (
∞), return-1.
Algorithm
- Initialize a distance array
distof sizenwith∞. - Set
dist[k - 1] = 0(source node). - Repeat
n - 1times:- For every edge
(u → v, w):- If
dist[u] + w < dist[v], updatedist[v].
- If
- For every edge
- Find the maximum value in
dist. - If the maximum is
∞, 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 -1Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of vertices and is the number of edges.
4. Shortest Path Faster Algorithm
Intuition
SPFA (Shortest Path Faster Algorithm) is an optimized version of Bellman–Ford.
Instead of relaxing all edges every time, we:
- Only re-process nodes whose distance was actually improved
- Use a queue to propagate distance updates efficiently
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.
Algorithm
- Build an adjacency list from
times. - Initialize
distfor all nodes as∞. - Set
dist[k] = 0and push(k, 0)into a queue. - While the queue is not empty:
- Pop
(node, time) - If
time > dist[node], skip it (outdated path) - For each neighbor:
- If
time + weight < dist[neighbor]:- Update
dist[neighbor] - Push
(neighbor, newTime)into the queue
- Update
- If
- Pop
- Take the maximum value in
dist. - If any node is unreachable (
∞), 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 -1Time & Space Complexity
- Time complexity: in average case, in worst case.
- Space complexity:
Where is the number of vertices and is the number of edges.
5. Dijkstra's Algorithm
Intuition
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:
- Always expand the node that currently has the smallest known time
- Once a node is picked from the min-heap, its shortest time is final
- Use a min-heap (priority queue) to always process the closest node next
By doing this, we gradually spread the signal in increasing order of time.
Algorithm
- Build an adjacency list from
times. - Use a min-heap initialized with
(0, k)(time, node). - Maintain a
visitedset to avoid reprocessing nodes. - While the heap is not empty:
- Pop the node with the smallest time.
- If already
visited, skip. - Mark it
visitedand update the current time. - Push all unvisited neighbors with updated times into the heap.
- If all
nnodes arevisited, return the maximum time used. - Otherwise, return
-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 -1Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of vertices and is the number of edges.
Common Pitfalls
Returning Max Instead of Checking Unreachable Nodes
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.
Using 1-Based vs 0-Based Indexing
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.
Revisiting Nodes Without Proper Checks
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.
Sign in to join the discussion