743. Network Delay Time
Problem Description
You have a network with n
nodes numbered from 1
to n
. You're given a list called times
containing travel times between nodes. Each entry times[i] = (ui, vi, wi)
represents a directed edge where:
ui
is the source nodevi
is the target nodewi
is the time required for a signal to travel from source to target
Your task is to send a signal from a starting node k
and determine the minimum time needed for all n
nodes to receive the signal.
The signal propagates through the network following the directed edges and their associated travel times. When a signal reaches a node, it can be forwarded to other connected nodes. The time for a node to receive the signal is the shortest possible time among all paths from node k
to that node.
Return the minimum time required for all nodes to receive the signal. If it's impossible for all nodes to receive the signal (some nodes are unreachable from k
), return -1
.
For example, if you have 4 nodes and the signal starts from node 2, with edges [[2,1,1], [2,3,1], [3,4,1]]
, the signal reaches:
- Node 1 at time 1 (directly from node 2)
- Node 3 at time 1 (directly from node 2)
- Node 4 at time 2 (from node 2 β node 3 β node 4)
- Node 2 at time 0 (starting point)
So the answer would be 2, as that's when the last node receives the signal.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem explicitly describes a network of nodes with directed edges between them. We have
n
nodes and edges defined bytimes[i] = (ui, vi, wi)
.
Is it a tree?
- No: The graph can have cycles and multiple paths between nodes. There's no constraint stating it's a tree structure (which would require exactly n-1 edges and no cycles).
Is the problem related to directed acyclic graphs?
- No: While the graph is directed, there's no guarantee it's acyclic. The problem doesn't mention anything about topological ordering or DAG properties.
Is the problem related to shortest paths?
- Yes: We need to find the minimum time for signals to reach all nodes from a starting node
k
. This is essentially finding the shortest path fromk
to every other node.
Is the graph weighted?
- Yes: Each edge has a weight
wi
representing the travel time for the signal.
Dijkstra's Algorithm
- This is our destination: For weighted graphs with shortest path requirements, Dijkstra's algorithm is the appropriate choice.
Conclusion: The flowchart suggests using Dijkstra's Algorithm for this problem. While the flowchart focuses on DFS patterns, this particular problem actually requires Dijkstra's algorithm (which the solution implements) rather than DFS. The solution uses Dijkstra's algorithm to find the shortest paths from node k
to all other nodes, then returns the maximum of these shortest distances to determine when all nodes have received the signal.
Intuition
Think of the network as a city where messages need to be delivered to all buildings. When we send a signal from node k
, it spreads outward like ripples in water, but the signal can only travel along the directed roads (edges) and each road takes a specific amount of time to traverse.
The key insight is that we need to find the fastest way to reach every single node from our starting point. This naturally leads us to think about shortest paths - we want to minimize the time it takes for the signal to reach each node.
Why can't we just use simple traversal like DFS or BFS? Because the edges have different weights (travel times). A path with fewer edges might take longer than a path with more edges if those edges have smaller weights. For instance, a direct path with weight 10 is slower than a two-hop path with weights 3 and 4.
The challenge becomes finding the shortest path to every node efficiently. We can't just explore all possible paths - that would be exponentially expensive. Instead, we need a systematic way to explore paths in order of their total distance from the source.
This is where Dijkstra's algorithm shines. It processes nodes in order of their distance from the source, guaranteeing that when we visit a node, we've found the shortest path to it. The algorithm maintains a "frontier" of nodes we haven't fully processed yet, always picking the closest unvisited node next.
The final piece of the puzzle is determining when all nodes have received the signal. Since signals travel simultaneously along all paths, the answer is the maximum among all shortest distances - that's when the furthest node finally receives the signal. If any node has an infinite distance (unreachable), we return -1
.
The solution implements this by:
- Building an adjacency matrix to represent the graph
- Using Dijkstra's algorithm to find shortest paths to all nodes
- Taking the maximum of all distances as the final answer
Learn more about Depth-First Search, Breadth-First Search, Graph, Shortest Path and Heap (Priority Queue) patterns.
Solution Approach
The solution implements the naive version of Dijkstra's algorithm to find the shortest paths from node k
to all other nodes in the network.
Step 1: Graph Representation
First, we build an adjacency matrix g
where g[u][v]
represents the edge weight from node u
to node v
. We initialize all values to infinity (inf
) to indicate no direct connection, then populate it with the given edges:
g = [[inf] * n for _ in range(n)]
for u, v, w in times:
g[u - 1][v - 1] = w
Note that we convert to 0-indexed by subtracting 1 from node numbers.
Step 2: Initialize Distance and Visited Arrays
We maintain two arrays:
dist[i]
: shortest distance from nodek
to nodei
(initially all infinity except the source)vis[i]
: whether nodei
has been permanently processed
dist = [inf] * n dist[k - 1] = 0 # Distance to source is 0 vis = [False] * n
Step 3: Main Dijkstra Loop
The algorithm runs for n
iterations, processing one node per iteration:
for _ in range(n):
t = -1
for j in range(n):
if not vis[j] and (t == -1 or dist[t] > dist[j]):
t = j
vis[t] = True
In each iteration:
- Find the unvisited node
t
with minimum distance - Mark
t
as visited
Step 4: Relaxation
After selecting node t
, we update distances to all other nodes through t
:
for j in range(n):
dist[j] = min(dist[j], dist[t] + g[t][j])
This checks if going through node t
provides a shorter path to node j
. If dist[t] + g[t][j] < dist[j]
, we update the distance.
Step 5: Return Result
Finally, we check the maximum distance among all nodes:
ans = max(dist)
return -1 if ans == inf else ans
If the maximum distance is infinity, it means some nodes are unreachable, so we return -1
. Otherwise, we return the maximum distance, which represents when the last node receives the signal.
Time Complexity: O(nΒ²)
due to the nested loops in finding the minimum distance node and relaxation.
Space Complexity: O(nΒ²)
for the adjacency matrix, though this could be optimized to O(E)
using an adjacency list where E is the number of edges.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with 4 nodes and starting node k = 2.
Input:
- n = 4, k = 2
- times = [[2,1,1], [2,3,1], [3,4,1]]
Step 1: Build the adjacency matrix
We create a 4Γ4 matrix initialized with infinity (β), then fill in the edges:
0 1 2 3 0 [ β β β β ] 1 [ 1 β 1 β ] (node 2 β node 1 with weight 1, node 2 β node 3 with weight 1) 2 [ β β β 1 ] (node 3 β node 4 with weight 1) 3 [ β β β β ]
Step 2: Initialize arrays
- dist = [β, 0, β, β] (distance to source node 2 is 0)
- vis = [False, False, False, False]
Step 3-4: Run Dijkstra's algorithm
Iteration 1:
- Find unvisited node with minimum distance: node 1 (index 1) with dist[1] = 0
- Mark vis[1] = True
- Relax edges from node 1:
- dist[0] = min(β, 0 + 1) = 1 (path: 2β1)
- dist[2] = min(β, 0 + 1) = 1 (path: 2β3)
- dist[3] = min(β, 0 + β) = β
- Updated dist = [1, 0, 1, β]
Iteration 2:
- Find unvisited node with minimum distance: node 0 or node 2 (both have distance 1, let's pick node 0)
- Mark vis[0] = True
- Relax edges from node 0:
- All edges from node 0 are β, so no updates
- dist remains [1, 0, 1, β]
Iteration 3:
- Find unvisited node with minimum distance: node 2 with dist[2] = 1
- Mark vis[2] = True
- Relax edges from node 2:
- dist[3] = min(β, 1 + 1) = 2 (path: 2β3β4)
- Updated dist = [1, 0, 1, 2]
Iteration 4:
- Find unvisited node with minimum distance: node 3 with dist[3] = 2
- Mark vis[3] = True
- Relax edges from node 3:
- All edges from node 3 are β, so no updates
- Final dist = [1, 0, 1, 2]
Step 5: Return result
- Maximum distance = max([1, 0, 1, 2]) = 2
- Since 2 β β, return 2
The signal reaches all nodes, with the furthest node (node 4) receiving it at time 2.
Solution Implementation
1class Solution:
2 def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
3 # Initialize adjacency matrix with infinity for all edges
4 # graph[i][j] represents the weight of edge from node i to node j
5 graph = [[float('inf')] * n for _ in range(n)]
6
7 # Build the directed weighted graph from the input
8 # Convert to 0-indexed (input uses 1-indexed nodes)
9 for source, target, weight in times:
10 graph[source - 1][target - 1] = weight
11
12 # Initialize distance array with infinity for all nodes
13 distances = [float('inf')] * n
14 # Set distance to starting node as 0
15 distances[k - 1] = 0
16
17 # Track visited nodes
18 visited = [False] * n
19
20 # Dijkstra's algorithm: process all n nodes
21 for _ in range(n):
22 # Find the unvisited node with minimum distance
23 min_node = -1
24 for node in range(n):
25 if not visited[node] and (min_node == -1 or distances[min_node] > distances[node]):
26 min_node = node
27
28 # Mark the selected node as visited
29 visited[min_node] = True
30
31 # Update distances to all neighbors of the selected node
32 for neighbor in range(n):
33 distances[neighbor] = min(distances[neighbor],
34 distances[min_node] + graph[min_node][neighbor])
35
36 # Find the maximum distance (time for signal to reach all nodes)
37 max_distance = max(distances)
38
39 # If any node is unreachable, return -1; otherwise return the max distance
40 return -1 if max_distance == float('inf') else max_distance
41
1class Solution {
2 public int networkDelayTime(int[][] times, int n, int k) {
3 // Initialize adjacency matrix for graph representation
4 int[][] graph = new int[n][n];
5 // Distance array to store shortest distances from source node
6 int[] distances = new int[n];
7 // Define infinity as a large value for unreachable nodes
8 final int INFINITY = 1 << 29;
9
10 // Initialize all distances to infinity
11 Arrays.fill(distances, INFINITY);
12
13 // Initialize adjacency matrix with infinity (no direct edges)
14 for (int[] row : graph) {
15 Arrays.fill(row, INFINITY);
16 }
17
18 // Build the graph from input edges
19 // times[i] = [source, target, weight]
20 for (int[] edge : times) {
21 int source = edge[0] - 1; // Convert to 0-indexed
22 int target = edge[1] - 1; // Convert to 0-indexed
23 int weight = edge[2];
24 graph[source][target] = weight;
25 }
26
27 // Set distance to source node as 0
28 distances[k - 1] = 0; // k-1 because nodes are 1-indexed
29
30 // Track visited nodes for Dijkstra's algorithm
31 boolean[] visited = new boolean[n];
32
33 // Dijkstra's algorithm: process all nodes
34 for (int i = 0; i < n; i++) {
35 // Find the unvisited node with minimum distance
36 int minNode = -1;
37 for (int j = 0; j < n; j++) {
38 if (!visited[j] && (minNode == -1 || distances[minNode] > distances[j])) {
39 minNode = j;
40 }
41 }
42
43 // Mark the selected node as visited
44 visited[minNode] = true;
45
46 // Update distances to all neighbors of the selected node
47 for (int j = 0; j < n; j++) {
48 distances[j] = Math.min(distances[j], distances[minNode] + graph[minNode][j]);
49 }
50 }
51
52 // Find the maximum distance among all nodes
53 int maxDistance = 0;
54 for (int distance : distances) {
55 maxDistance = Math.max(maxDistance, distance);
56 }
57
58 // If any node is unreachable, return -1; otherwise return max distance
59 return maxDistance == INFINITY ? -1 : maxDistance;
60 }
61}
62
1class Solution {
2public:
3 int networkDelayTime(vector<vector<int>>& times, int n, int k) {
4 // Initialize infinity value for unreachable nodes
5 const int INF = 1 << 29;
6
7 // Build adjacency matrix for the graph
8 // g[i][j] represents the weight of edge from node i to node j
9 vector<vector<int>> graph(n, vector<int>(n, INF));
10 for (const auto& edge : times) {
11 int from = edge[0] - 1; // Convert to 0-indexed
12 int to = edge[1] - 1; // Convert to 0-indexed
13 int weight = edge[2];
14 graph[from][to] = weight;
15 }
16
17 // Initialize distance array to track shortest distances from source
18 vector<int> distance(n, INF);
19 distance[k - 1] = 0; // Distance to source node is 0
20
21 // Track visited nodes
22 vector<bool> visited(n, false);
23
24 // Dijkstra's algorithm implementation
25 for (int i = 0; i < n; ++i) {
26 // Find the unvisited node with minimum distance
27 int minNode = -1;
28 for (int j = 0; j < n; ++j) {
29 if (!visited[j] && (minNode == -1 || distance[minNode] > distance[j])) {
30 minNode = j;
31 }
32 }
33
34 // Mark the selected node as visited
35 visited[minNode] = true;
36
37 // Update distances to all neighbors of the selected node
38 for (int neighbor = 0; neighbor < n; ++neighbor) {
39 distance[neighbor] = min(distance[neighbor],
40 distance[minNode] + graph[minNode][neighbor]);
41 }
42 }
43
44 // Find the maximum distance among all nodes
45 int maxDistance = *max_element(distance.begin(), distance.end());
46
47 // If any node is unreachable, return -1; otherwise return the maximum distance
48 return maxDistance == INF ? -1 : maxDistance;
49 }
50};
51
1/**
2 * Calculates the minimum time for a signal to reach all nodes in a network
3 * using Dijkstra's algorithm
4 *
5 * @param times - Array of [source, target, time] representing directed edges
6 * @param n - Total number of nodes in the network (labeled from 1 to n)
7 * @param k - Starting node for signal transmission
8 * @returns Minimum time for signal to reach all nodes, or -1 if impossible
9 */
10function networkDelayTime(times: number[][], n: number, k: number): number {
11 // Initialize adjacency matrix with infinity (no direct connection)
12 const adjacencyMatrix: number[][] = Array.from(
13 { length: n },
14 () => Array(n).fill(Infinity)
15 );
16
17 // Build the graph from input edges (converting 1-indexed to 0-indexed)
18 for (const [source, target, weight] of times) {
19 adjacencyMatrix[source - 1][target - 1] = weight;
20 }
21
22 // Initialize distances from source node to all other nodes
23 const distances: number[] = Array(n).fill(Infinity);
24 distances[k - 1] = 0; // Distance to source node is 0
25
26 // Track visited nodes during Dijkstra's algorithm
27 const visited: boolean[] = Array(n).fill(false);
28
29 // Process all nodes using Dijkstra's algorithm
30 for (let iteration = 0; iteration < n; ++iteration) {
31 // Find the unvisited node with minimum distance
32 let minDistanceNode = -1;
33 for (let node = 0; node < n; ++node) {
34 if (!visited[node] &&
35 (minDistanceNode === -1 || distances[node] < distances[minDistanceNode])) {
36 minDistanceNode = node;
37 }
38 }
39
40 // Mark the selected node as visited
41 visited[minDistanceNode] = true;
42
43 // Update distances to all neighbors of the selected node
44 for (let neighbor = 0; neighbor < n; ++neighbor) {
45 distances[neighbor] = Math.min(
46 distances[neighbor],
47 distances[minDistanceNode] + adjacencyMatrix[minDistanceNode][neighbor]
48 );
49 }
50 }
51
52 // Find the maximum distance (time for signal to reach the farthest node)
53 const maxDistance = Math.max(...distances);
54
55 // Return -1 if any node is unreachable, otherwise return the maximum distance
56 return maxDistance === Infinity ? -1 : maxDistance;
57}
58
Time and Space Complexity
The time complexity is O(n^2)
, where n
is the number of nodes. This is because:
- Building the adjacency matrix takes
O(m)
time wherem
is the number of edges - The main algorithm performs
n
iterations - In each iteration, it finds the minimum unvisited node in
O(n)
time - Then it updates distances to all
n
nodes inO(n)
time - Total:
O(m) + O(n Γ (n + n)) = O(m + n^2) = O(n^2)
sincem β€ n^2
in the worst case
The space complexity is O(n^2)
. This is because:
- The adjacency matrix
g
usesO(n^2)
space - The distance array
dist
usesO(n)
space - The visited array
vis
usesO(n)
space - Total:
O(n^2 + n + n) = O(n^2)
Note: While the reference answer states O(n^2 + m)
for time complexity, this simplifies to O(n^2)
since the quadratic term dominates when considering that m
can be at most n^2
edges in a directed graph.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Index Confusion Between 1-indexed and 0-indexed
The most frequent mistake is forgetting that the problem uses 1-indexed nodes while Python arrays are 0-indexed. This leads to incorrect graph construction or array access errors.
Pitfall Example:
# Wrong - forgetting to convert indices for u, v, w in times: graph[u][v] = w # IndexError or wrong mapping! # Wrong - forgetting to convert starting node distances[k] = 0 # Should be k-1
Solution: Always subtract 1 when converting from node numbers to array indices:
for u, v, w in times: graph[u - 1][v - 1] = w distances[k - 1] = 0
2. Handling Self-loops and Multiple Edges
The naive implementation doesn't properly handle cases where there might be multiple edges between the same pair of nodes or self-loops.
Pitfall Example:
# If times = [[1,2,5], [1,2,3]], the second edge overwrites the first for u, v, w in times: graph[u - 1][v - 1] = w # Later edges overwrite earlier ones
Solution: Keep only the minimum weight edge between any two nodes:
for u, v, w in times:
graph[u - 1][v - 1] = min(graph[u - 1][v - 1], w)
3. Inefficient Node Selection
The O(n) linear search for the minimum distance node in each iteration makes the overall complexity O(nΒ²). For sparse graphs, this is inefficient.
Pitfall Example:
# This works but is inefficient for large sparse graphs
for _ in range(n):
min_node = -1
for node in range(n):
if not visited[node] and (min_node == -1 or distances[min_node] > distances[node]):
min_node = node
Solution: Use a min-heap (priority queue) for O(log n) extraction:
import heapq
def networkDelayTime(self, times, n, k):
# Build adjacency list instead of matrix
graph = defaultdict(list)
for u, v, w in times:
graph[u].append((v, w))
# Use heap for efficient minimum extraction
heap = [(0, k)]
distances = {}
while heap:
dist, node = heapq.heappop(heap)
if node in distances:
continue
distances[node] = dist
for neighbor, weight in graph[node]:
if neighbor not in distances:
heapq.heappush(heap, (dist + weight, neighbor))
return max(distances.values()) if len(distances) == n else -1
4. Memory Inefficiency with Adjacency Matrix
Using an nΓn matrix wastes memory when the graph is sparse (few edges compared to nΒ²).
Pitfall Example:
# Uses O(nΒ²) space even if there are only a few edges
graph = [[float('inf')] * n for _ in range(n)]
Solution: Use an adjacency list representation:
from collections import defaultdict
graph = defaultdict(list)
for u, v, w in times:
graph[u - 1].append((v - 1, w))
5. Not Checking for Unreachable Nodes Early
The current implementation processes all nodes even if some are clearly unreachable, wasting computation time.
Pitfall Example:
# Continues processing even when min_node has infinite distance
for _ in range(n):
# ... find min_node ...
visited[min_node] = True # Processes nodes with infinite distance
Solution: Break early if the minimum distance node has infinite distance:
for _ in range(n):
min_node = -1
for node in range(n):
if not visited[node] and (min_node == -1 or distances[min_node] > distances[node]):
min_node = node
# Early termination if remaining nodes are unreachable
if distances[min_node] == float('inf'):
break
visited[min_node] = True
# ... rest of the algorithm ...
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Want a Structured Path to Master System Design Too? Donβt Miss This!