Facebook Pixel

2039. The Time When the Network Becomes Idle

Problem Description

You have a network of n servers labeled from 0 to n - 1. The network connections are given as a 2D array edges, where edges[i] = [ui, vi] means servers ui and vi can directly communicate with each other in 1 second. The network is fully connected, meaning any server can reach any other server through direct or indirect connections.

Server 0 is the master server that processes messages. All other servers (1 to n-1) are data servers that need to send messages to the master for processing.

At second 0, each data server sends its initial message to the master server. Messages travel optimally (shortest path) to reach the master. The master processes messages instantly upon arrival and sends replies back through the same path.

Each data server has a patience value given in the array patience. Starting from second 1, at the beginning of each second, every data server checks if it has received a reply:

  • If no reply has been received, the server will resend its message every patience[i] seconds since the last send
  • If a reply has been received, the server stops sending messages

For example, if server i has patience[i] = 3 and hasn't received a reply, it will send messages at seconds 0, 3, 6, 9, etc., until it gets a reply.

The network becomes "idle" when no messages are traveling between servers or arriving at any server.

Your task is to find the earliest second when the network becomes completely idle.

The key insight is that for each data server i:

  • The shortest round-trip time is 2 * di where di is the shortest distance to the master
  • The last message is sent at time ((2 * di - 1) // patience[i]) * patience[i]
  • This last message arrives back at time ((2 * di - 1) // patience[i]) * patience[i] + 2 * di
  • The network becomes idle 1 second after all messages have been processed

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: We have servers (nodes) connected by edges representing communication channels. The problem explicitly gives us a network structure with n servers and edges array defining connections.

Is it a tree?

  • No: While the graph is connected, it's not necessarily a tree. The problem states the network is fully connected but doesn't guarantee a tree structure (no cycles restriction).

Is the problem related to directed acyclic graphs?

  • No: The graph is undirected (messages can pass in both directions between servers), not a DAG.

Is the problem related to shortest paths?

  • Yes: We need to find the shortest path from each data server to the master server (server 0) to determine the optimal message travel time. The problem states "messages move between servers optimally" and we need "the least amount of time."

Is the graph weighted?

  • No: Each edge has the same weight (1 second travel time between directly connected servers). All edges represent equal communication time.

BFS

  • Yes: Since we have an unweighted graph and need shortest paths from a single source (server 0) to all other nodes, BFS is the appropriate algorithm.

Conclusion: The flowchart suggests using BFS (Breadth-First Search) for finding the shortest paths from the master server to all data servers in this unweighted graph problem. BFS will help us determine the minimum distance from server 0 to each data server, which is crucial for calculating when each server receives its reply and when the network becomes idle.

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

Intuition

To understand when the network becomes idle, we need to track when the last message finishes traveling through the network.

Let's think about what happens for a single data server i:

  1. At time 0, it sends its first message
  2. The message takes d seconds to reach the master (where d is the shortest distance)
  3. The master instantly processes and sends a reply back
  4. The reply takes another d seconds to return, so the total round-trip time is 2*d seconds

But here's the twist - the server doesn't wait! If it doesn't receive a reply by time patience[i], it resends the message. This creates a pattern:

  • Server sends at time 0
  • If patience[i] < 2*d, it sends again at time patience[i]
  • It keeps sending every patience[i] seconds until it gets the reply at time 2*d

The critical insight is finding when the last resent message gets its reply back. If a server last sends a message at time t, that message will return at time t + 2*d.

To find the last send time:

  • The server receives its first reply at time 2*d
  • It stops sending after receiving the reply
  • The last message is sent at the last multiple of patience[i] before time 2*d
  • This is: ((2*d - 1) // patience[i]) * patience[i]

Why 2*d - 1? Because if the reply arrives exactly at time 2*d, the server checks at the beginning of that second and sees the reply, so it won't send at time 2*d.

The last message sent at time ((2*d - 1) // patience[i]) * patience[i] will arrive back at time ((2*d - 1) // patience[i]) * patience[i] + 2*d. Adding 1 second for the network to process and become idle gives us the final formula.

Since we need the shortest distance d from each server to the master, and the graph is unweighted (each edge takes 1 second), BFS from server 0 is perfect for finding these distances efficiently. We then calculate the idle time for each server and take the maximum.

Learn more about Breadth-First Search and Graph patterns.

Solution Approach

The implementation follows the BFS approach we identified to find shortest distances from the master server to all data servers.

Step 1: Build the Graph

g = defaultdict(list)
for u, v in edges:
    g[u].append(v)
    g[v].append(u)

We construct an undirected graph using an adjacency list. Since messages can travel in both directions, we add edges in both directions.

Step 2: Initialize BFS

q = deque([0])
vis = {0}
ans = d = 0
  • q: BFS queue starting with the master server (node 0)
  • vis: Set to track visited nodes (avoiding revisits)
  • d: Current distance/depth in BFS
  • ans: Maximum idle time across all servers

Step 3: Level-by-Level BFS

while q:
    d += 1
    t = d * 2
    for _ in range(len(q)):
        u = q.popleft()
        for v in g[u]:
            if v not in vis:
                vis.add(v)
                q.append(v)
                ans = max(ans, (t - 1) // patience[v] * patience[v] + t + 1)

The BFS processes nodes level by level:

  • d represents the current distance from server 0
  • t = d * 2 is the round-trip time for servers at distance d
  • For each unvisited neighbor v, we calculate when it becomes idle

Step 4: Calculate Idle Time for Each Server

For each data server v at distance d from the master:

  • Round-trip time: t = 2 * d
  • Last message send time: (t - 1) // patience[v] * patience[v]
    • This finds the last multiple of patience[v] before the reply arrives
  • Reply arrival time: (t - 1) // patience[v] * patience[v] + t
  • Network idle time: Add 1 second for processing

The formula (t - 1) // patience[v] * patience[v] + t + 1 combines all these calculations.

Step 5: Return Maximum Idle Time

Since the network becomes idle only when ALL servers are idle, we track the maximum idle time across all servers using ans = max(ans, ...).

The algorithm efficiently finds the shortest path to each server in O(V + E) time using BFS, then calculates the idle time for each server in constant time, giving us an overall time complexity of O(V + E).

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 to illustrate the solution approach.

Example Input:

  • n = 5 (servers labeled 0-4)
  • edges = [[0,1], [1,2], [2,3], [1,4]]
  • patience = [0, 2, 1, 2, 3] (patience[0] is unused since server 0 is the master)

Step 1: Build the Graph

First, we create an adjacency list representation:

Graph:
0: [1]
1: [0, 2, 4]
2: [1, 3]
3: [2]
4: [1]

The network looks like:

    0 --- 1 --- 2 --- 3
          |
          4

Step 2: BFS to Find Distances

Starting BFS from server 0:

  • Level 0 (d=0): Server 0 (master)
  • Level 1 (d=1): Server 1
  • Level 2 (d=2): Servers 2 and 4
  • Level 3 (d=3): Server 3

So the distances are:

  • Server 1: distance = 1, round-trip = 2 seconds
  • Server 2: distance = 2, round-trip = 4 seconds
  • Server 3: distance = 3, round-trip = 6 seconds
  • Server 4: distance = 2, round-trip = 4 seconds

Step 3: Calculate Idle Time for Each Server

Server 1 (distance=1, round-trip=2, patience=2):

  • Sends at time 0
  • Would resend at time 2 (patience=2), but reply arrives at time 2
  • Last send time: ((2-1) // 2) * 2 = 0
  • Reply arrives back at: 0 + 2 = 2
  • Idle at: 2 + 1 = 3

Server 2 (distance=2, round-trip=4, patience=1):

  • Sends at times: 0, 1, 2, 3 (every 1 second until reply at time 4)
  • Last send time: ((4-1) // 1) * 1 = 3
  • Reply arrives back at: 3 + 4 = 7
  • Idle at: 7 + 1 = 8

Server 3 (distance=3, round-trip=6, patience=2):

  • Sends at times: 0, 2, 4 (every 2 seconds until reply at time 6)
  • Last send time: ((6-1) // 2) * 2 = 4
  • Reply arrives back at: 4 + 6 = 10
  • Idle at: 10 + 1 = 11

Server 4 (distance=2, round-trip=4, patience=3):

  • Sends at times: 0, 3 (every 3 seconds, but reply arrives at time 4)
  • Last send time: ((4-1) // 3) * 3 = 3
  • Reply arrives back at: 3 + 4 = 7
  • Idle at: 7 + 1 = 8

Step 4: Find Maximum Idle Time

The idle times are:

  • Server 1: 3 seconds
  • Server 2: 8 seconds
  • Server 3: 11 seconds
  • Server 4: 8 seconds

The network becomes completely idle at the maximum: 11 seconds

This matches our formula: For each server, we calculate ((2*d - 1) // patience[i]) * patience[i] + 2*d + 1, then take the maximum across all servers.

Solution Implementation

1class Solution:
2    def networkBecomesIdle(self, edges: List[List[int]], patience: List[int]) -> int:
3        # Build adjacency list representation of the graph
4        graph = defaultdict(list)
5        for node_u, node_v in edges:
6            graph[node_u].append(node_v)
7            graph[node_v].append(node_u)
8      
9        # Initialize BFS from master server (node 0)
10        queue = deque([0])
11        visited = {0}
12        max_idle_time = 0
13        distance = 0
14      
15        # BFS to find shortest distance from node 0 to all other nodes
16        while queue:
17            distance += 1
18            round_trip_time = distance * 2  # Time for message to go and come back
19          
20            # Process all nodes at current distance level
21            for _ in range(len(queue)):
22                current_node = queue.popleft()
23              
24                # Explore neighbors
25                for neighbor in graph[current_node]:
26                    if neighbor not in visited:
27                        visited.add(neighbor)
28                        queue.append(neighbor)
29                      
30                        # Calculate when this server becomes idle
31                        # Last resend happens at: (round_trip_time - 1) // patience[neighbor] * patience[neighbor]
32                        # This message arrives back at: last_resend_time + round_trip_time
33                        # Network becomes idle 1 second after last message arrives
34                        last_resend_time = (round_trip_time - 1) // patience[neighbor] * patience[neighbor]
35                        idle_time = last_resend_time + round_trip_time + 1
36                        max_idle_time = max(max_idle_time, idle_time)
37      
38        return max_idle_time
39
1class Solution {
2    public int networkBecomesIdle(int[][] edges, int[] patience) {
3        int numberOfServers = patience.length;
4      
5        // Build adjacency list representation of the network graph
6        List<Integer>[] adjacencyList = new List[numberOfServers];
7        Arrays.setAll(adjacencyList, index -> new ArrayList<>());
8      
9        // Add bidirectional edges to the graph
10        for (int[] edge : edges) {
11            int server1 = edge[0];
12            int server2 = edge[1];
13            adjacencyList[server1].add(server2);
14            adjacencyList[server2].add(server1);
15        }
16      
17        // Initialize BFS queue starting from master server (server 0)
18        Deque<Integer> bfsQueue = new ArrayDeque<>();
19        bfsQueue.offer(0);
20      
21        // Track visited servers to avoid revisiting
22        boolean[] visited = new boolean[numberOfServers];
23        visited[0] = true;
24      
25        int maxIdleTime = 0;
26        int currentDistance = 0;
27      
28        // Perform BFS to find shortest distance from server 0 to all other servers
29        while (!bfsQueue.isEmpty()) {
30            currentDistance++;
31            int roundTripTime = currentDistance * 2;
32          
33            // Process all servers at current distance level
34            int serversAtCurrentLevel = bfsQueue.size();
35            for (int i = 0; i < serversAtCurrentLevel; i++) {
36                int currentServer = bfsQueue.poll();
37              
38                // Explore all neighbors of current server
39                for (int neighborServer : adjacencyList[currentServer]) {
40                    if (!visited[neighborServer]) {
41                        visited[neighborServer] = true;
42                        bfsQueue.offer(neighborServer);
43                      
44                        // Calculate when this server becomes idle
45                        // Last resend time: ((roundTripTime - 1) / patience) * patience
46                        // Message arrives back at: roundTripTime
47                        // Server becomes idle at: last resend time + roundTripTime + 1
48                        int lastResendTime = ((roundTripTime - 1) / patience[neighborServer]) * patience[neighborServer];
49                        int idleTime = lastResendTime + roundTripTime + 1;
50                        maxIdleTime = Math.max(maxIdleTime, idleTime);
51                    }
52                }
53            }
54        }
55      
56        return maxIdleTime;
57    }
58}
59
1class Solution {
2public:
3    int networkBecomesIdle(vector<vector<int>>& edges, vector<int>& patience) {
4        int n = patience.size();
5      
6        // Build adjacency list representation of the graph
7        vector<vector<int>> graph(n);
8        for (auto& edge : edges) {
9            int u = edge[0];
10            int v = edge[1];
11            graph[u].push_back(v);
12            graph[v].push_back(u);
13        }
14      
15        // BFS to find shortest distance from node 0 to all other nodes
16        queue<int> bfsQueue;
17        bfsQueue.push(0);
18      
19        // Track visited nodes to avoid revisiting
20        vector<bool> visited(n, false);
21        visited[0] = true;
22      
23        int maxIdleTime = 0;
24        int distance = 0;
25      
26        // Level-order BFS traversal
27        while (!bfsQueue.empty()) {
28            distance++;
29            int roundTripTime = distance * 2;  // Time for message to go and come back
30          
31            // Process all nodes at current distance level
32            int levelSize = bfsQueue.size();
33            for (int i = 0; i < levelSize; i++) {
34                int currentNode = bfsQueue.front();
35                bfsQueue.pop();
36              
37                // Explore all neighbors
38                for (int neighbor : graph[currentNode]) {
39                    if (!visited[neighbor]) {
40                        visited[neighbor] = true;
41                        bfsQueue.push(neighbor);
42                      
43                        // Calculate when this server becomes idle
44                        // Last message sent at: (roundTripTime - 1) / patience[neighbor] * patience[neighbor]
45                        // Last message arrives back at: lastSentTime + roundTripTime
46                        // Server becomes idle at: lastArrivalTime + 1
47                        int lastMessageSentTime = (roundTripTime - 1) / patience[neighbor] * patience[neighbor];
48                        int idleTime = lastMessageSentTime + roundTripTime + 1;
49                        maxIdleTime = max(maxIdleTime, idleTime);
50                    }
51                }
52            }
53        }
54      
55        return maxIdleTime;
56    }
57};
58
1/**
2 * Calculates when the network becomes idle after all data servers have received replies
3 * @param edges - Array of edges representing network connections between servers
4 * @param patience - Array where patience[i] is the resend interval for server i
5 * @returns The earliest second when the network becomes idle
6 */
7function networkBecomesIdle(edges: number[][], patience: number[]): number {
8    const serverCount = patience.length;
9  
10    // Build adjacency list representation of the network graph
11    const adjacencyList: number[][] = Array.from({ length: serverCount }, () => []);
12    for (const [serverA, serverB] of edges) {
13        adjacencyList[serverA].push(serverB);
14        adjacencyList[serverB].push(serverA);
15    }
16  
17    // Track visited servers during BFS traversal
18    const visited: boolean[] = Array.from({ length: serverCount }, () => false);
19    visited[0] = true; // Master server is initially visited
20  
21    // Initialize BFS queue with master server
22    let currentLevelQueue: number[] = [0];
23    let maxIdleTime = 0;
24  
25    // Perform level-order BFS to find shortest paths from master server
26    for (let distance = 1; currentLevelQueue.length > 0; distance++) {
27        // Calculate round-trip time for current distance
28        const roundTripTime = distance * 2;
29        const nextLevelQueue: number[] = [];
30      
31        // Process all servers at current distance level
32        for (const currentServer of currentLevelQueue) {
33            for (const neighborServer of adjacencyList[currentServer]) {
34                if (!visited[neighborServer]) {
35                    visited[neighborServer] = true;
36                    nextLevelQueue.push(neighborServer);
37                  
38                    // Calculate when this server becomes idle:
39                    // - Last message sent at: floor((roundTripTime - 1) / patience) * patience
40                    // - Reply arrives at: roundTripTime
41                    // - Server becomes idle at: last message sent + roundTripTime + 1
42                    const lastMessageSentTime = Math.floor((roundTripTime - 1) / patience[neighborServer]) * patience[neighborServer];
43                    const serverIdleTime = lastMessageSentTime + roundTripTime + 1;
44                    maxIdleTime = Math.max(maxIdleTime, serverIdleTime);
45                }
46            }
47        }
48      
49        // Move to next level
50        currentLevelQueue = nextLevelQueue;
51    }
52  
53    return maxIdleTime;
54}
55

Time and Space Complexity

Time Complexity: O(n + m) where n is the number of nodes and m is the number of edges.

The algorithm performs a BFS traversal starting from node 0. In the BFS:

  • Each node is visited exactly once (controlled by the vis set)
  • Each edge is traversed at most twice (once from each endpoint when exploring neighbors)
  • For each node visited, we iterate through its adjacent nodes

Since we visit each node once and traverse each edge at most twice, the time complexity is O(n + m).

However, in many network problems, the graph is connected and m is often O(n) (especially for tree-like structures where m = n - 1), so the time complexity can be simplified to O(n) in practical scenarios.

Space Complexity: O(n + m)

The space usage includes:

  • Graph adjacency list g: stores all edges, requiring O(m) space
  • BFS queue q: can contain at most O(n) nodes in the worst case
  • Visited set vis: stores up to n nodes, requiring O(n) space
  • Other variables use O(1) space

The total space complexity is O(n + m), which can be simplified to O(n) when the graph structure ensures m = O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Last Message Send Time Calculation

Pitfall: A common mistake is incorrectly calculating when the last message is sent. Developers often use (round_trip_time // patience[i]) * patience[i] instead of ((round_trip_time - 1) // patience[i]) * patience[i].

Why it's wrong: If the round-trip time is exactly divisible by patience, this formula would incorrectly include an extra unnecessary resend. For example, if round-trip time is 6 and patience is 3, the message is sent at times 0 and 3. The reply arrives at time 6, so there's no need for another resend at time 6. Using the wrong formula would assume a resend at time 6.

Solution: Always use (round_trip_time - 1) // patience[i] * patience[i] to find the last resend time before the reply arrives.

2. Missing the +1 for Network Idle Time

Pitfall: Forgetting to add 1 second after the last message arrives to determine when the network becomes idle. The code might return last_resend_time + round_trip_time instead of last_resend_time + round_trip_time + 1.

Why it's wrong: The problem states the network becomes idle when no messages are traveling or arriving. Even after the last message arrives, we need one more second to ensure no messages are in transit.

Solution: Always add 1 to the arrival time of the last message: idle_time = last_resend_time + round_trip_time + 1

3. Using DFS Instead of BFS for Shortest Path

Pitfall: Using DFS to find the distance from the master server to other servers.

Why it's wrong: DFS doesn't guarantee the shortest path in an unweighted graph. It might find a longer path first, leading to incorrect round-trip time calculations.

Solution: Always use BFS for finding shortest paths in unweighted graphs, as it explores nodes level by level, guaranteeing the shortest distance.

4. Forgetting to Handle the Master Server (Node 0)

Pitfall: Including node 0 in the idle time calculations or trying to access patience[0] which doesn't exist.

Why it's wrong: The master server doesn't send messages to itself and doesn't have a patience value. The patience array starts from index 1 for server 1.

Solution: Start BFS from node 0 but only calculate idle times for its neighbors (data servers). Mark node 0 as visited immediately to avoid processing it.

5. Integer Division vs Float Division

Pitfall: Using float division (/) instead of integer division (//) when calculating the last resend time.

Why it's wrong: Message sending happens at discrete integer time points. Using float division would give fractional seconds, which don't exist in this problem.

Solution: Always use integer division: (round_trip_time - 1) // patience[neighbor]

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

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

Load More