Facebook Pixel

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 node
  • vi is the target node
  • wi 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 by times[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 from k 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Building an adjacency matrix to represent the graph
  2. Using Dijkstra's algorithm to find shortest paths to all nodes
  3. 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 node k to node i (initially all infinity except the source)
  • vis[i]: whether node i 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 Evaluator

Example 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 where m 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 in O(n) time
  • Total: O(m) + O(n Γ— (n + n)) = O(m + n^2) = O(n^2) since m ≀ n^2 in the worst case

The space complexity is O(n^2). This is because:

  • The adjacency matrix g uses O(n^2) space
  • The distance array dist uses O(n) space
  • The visited array vis uses O(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 ...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More