Facebook Pixel

1615. Maximal Network Rank

Problem Description

You are given n cities numbered from 0 to n-1 and an array roads where each roads[i] = [ai, bi] represents a bidirectional road connecting city ai and city bi.

The network rank of two different cities is defined as the total number of roads that are directly connected to either of the two cities. If a road directly connects both cities, it is counted only once (not twice).

Your task is to find the maximal network rank of the infrastructure, which is the maximum network rank among all possible pairs of different cities.

For example, if city A has 3 roads connected to it and city B has 4 roads connected to it:

  • If there is a direct road between A and B, their network rank would be 3 + 4 - 1 = 6 (subtract 1 because the road between them is counted in both cities' connections)
  • If there is no direct road between A and B, their network rank would be 3 + 4 = 7

The goal is to examine all possible pairs of cities and return the highest network rank found.

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

Intuition

To find the maximal network rank, we need to understand what contributes to the network rank of any two cities. For any pair of cities, their network rank is the sum of roads connected to each city. However, if the two cities are directly connected to each other, we've counted that shared road twice (once for each city), so we need to subtract it once.

This leads us to a straightforward approach: we need to know two pieces of information:

  1. How many roads are connected to each city (the degree of each city)
  2. Whether any two cities are directly connected

With this information, for any pair of cities (a, b), we can calculate their network rank as:

  • degree[a] + degree[b] - 1 if cities a and b are directly connected
  • degree[a] + degree[b] if cities a and b are not directly connected

Since we want the maximal network rank, we need to check all possible pairs of cities. For n cities, there are n * (n-1) / 2 unique pairs to check.

The key insight is that we don't need any complex graph algorithms here - just simple counting and checking. We can preprocess the graph to store each city's connections in a set (for quick lookup of whether two cities are connected) and then systematically check every pair to find the maximum network rank.

This gives us the formula: network_rank = len(connections[a]) + len(connections[b]) - (1 if a in connections[b] else 0)

Learn more about Graph patterns.

Solution Approach

The implementation uses a graph representation to efficiently solve this problem. Let's walk through the key components:

Data Structure Setup: We use a defaultdict(set) to store the graph g, where each key represents a city and its value is a set containing all cities directly connected to it. Using a set allows O(1) lookup time to check if two cities are connected.

Building the Graph: For each road [a, b] in the input:

  • Add city b to the set of connections for city a: g[a].add(b)
  • Add city a to the set of connections for city b: g[b].add(a)

This creates a bidirectional representation of the road network.

Finding Maximum Network Rank: We enumerate all possible pairs of cities using nested loops:

  • Outer loop: for a in range(n) - iterates through each city
  • Inner loop: for b in range(a + 1, n) - ensures we only check each pair once (avoiding duplicates like (a,b) and (b,a))

For each pair (a, b), we calculate the network rank using the formula:

network_rank = len(g[a]) + len(g[b]) - (a in g[b])

Here's what each component means:

  • len(g[a]): number of roads connected to city a
  • len(g[b]): number of roads connected to city b
  • (a in g[b]): returns True (1) if cities are directly connected, False (0) otherwise

The expression (a in g[b]) cleverly handles the subtraction: if the cities are connected, we subtract 1 to avoid double-counting their shared road; otherwise, we subtract 0.

Time Complexity: O(nΒ²) for checking all pairs, where n is the number of cities Space Complexity: O(E) where E is the number of roads, for storing the graph

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 cities (numbered 0-3) and the following roads:

  • roads = [[0,1], [0,3], [1,2], [1,3]]

Step 1: Build the Graph

We create our adjacency list using a defaultdict(set):

  • City 0 connects to: {1, 3}
  • City 1 connects to: {0, 2, 3}
  • City 2 connects to: {1}
  • City 3 connects to: {0, 1}

Step 2: Calculate Network Rank for Each Pair

Now we check all possible pairs of cities:

Pair (0, 1):

  • City 0 has 2 connections
  • City 1 has 3 connections
  • They are directly connected (0 is in city 1's set)
  • Network rank = 2 + 3 - 1 = 4

Pair (0, 2):

  • City 0 has 2 connections
  • City 2 has 1 connection
  • They are NOT directly connected
  • Network rank = 2 + 1 - 0 = 3

Pair (0, 3):

  • City 0 has 2 connections
  • City 3 has 2 connections
  • They are directly connected (0 is in city 3's set)
  • Network rank = 2 + 2 - 1 = 3

Pair (1, 2):

  • City 1 has 3 connections
  • City 2 has 1 connection
  • They are directly connected (1 is in city 2's set)
  • Network rank = 3 + 1 - 1 = 3

Pair (1, 3):

  • City 1 has 3 connections
  • City 3 has 2 connections
  • They are directly connected (1 is in city 3's set)
  • Network rank = 3 + 2 - 1 = 4

Pair (2, 3):

  • City 2 has 1 connection
  • City 3 has 2 connections
  • They are NOT directly connected
  • Network rank = 1 + 2 - 0 = 3

Step 3: Find Maximum

The maximum network rank among all pairs is 4, which occurs for pairs (0,1) and (1,3).

The formula len(g[a]) + len(g[b]) - (a in g[b]) elegantly handles both cases:

  • When cities are connected: the boolean expression evaluates to True (1), subtracting the shared road
  • When cities aren't connected: the boolean expression evaluates to False (0), adding both cities' full connection counts

Solution Implementation

1from collections import defaultdict
2from typing import List
3
4class Solution:
5    def maximalNetworkRank(self, n: int, roads: List[List[int]]) -> int:
6        # Build adjacency list using sets to store connected cities
7        graph = defaultdict(set)
8        for city_a, city_b in roads:
9            graph[city_a].add(city_b)
10            graph[city_b].add(city_a)
11      
12        max_rank = 0
13      
14        # Check all pairs of cities
15        for city_a in range(n):
16            for city_b in range(city_a + 1, n):
17                # Calculate network rank for this pair
18                # Total roads connected to both cities minus the shared road (if exists)
19                current_rank = len(graph[city_a]) + len(graph[city_b])
20              
21                # If cities are directly connected, subtract 1 to avoid double counting
22                if city_a in graph[city_b]:
23                    current_rank -= 1
24              
25                # Update maximum rank if current is greater
26                max_rank = max(max_rank, current_rank)
27      
28        return max_rank
29
1class Solution {
2    public int maximalNetworkRank(int n, int[][] roads) {
3        // Create adjacency matrix to track direct connections between cities
4        int[][] adjacencyMatrix = new int[n][n];
5      
6        // Array to store the degree (number of connections) for each city
7        int[] degree = new int[n];
8      
9        // Build the graph: populate adjacency matrix and count degrees
10        for (int[] road : roads) {
11            int city1 = road[0];
12            int city2 = road[1];
13          
14            // Mark the bidirectional connection in the adjacency matrix
15            adjacencyMatrix[city1][city2] = 1;
16            adjacencyMatrix[city2][city1] = 1;
17          
18            // Increment the degree count for both cities
19            degree[city1]++;
20            degree[city2]++;
21        }
22      
23        int maxRank = 0;
24      
25        // Try all possible pairs of cities
26        for (int city1 = 0; city1 < n; city1++) {
27            for (int city2 = city1 + 1; city2 < n; city2++) {
28                // Calculate network rank for this pair
29                // Total roads = roads from city1 + roads from city2 - shared road (if exists)
30                int currentRank = degree[city1] + degree[city2] - adjacencyMatrix[city1][city2];
31              
32                // Update maximum network rank
33                maxRank = Math.max(maxRank, currentRank);
34            }
35        }
36      
37        return maxRank;
38    }
39}
40
1class Solution {
2public:
3    int maximalNetworkRank(int n, vector<vector<int>>& roads) {
4        // Array to store the degree (number of connections) for each city
5        int degree[n];
6        // 2D array to represent adjacency matrix (1 if cities are connected, 0 otherwise)
7        int adjacencyMatrix[n][n];
8      
9        // Initialize arrays with zeros
10        memset(degree, 0, sizeof(degree));
11        memset(adjacencyMatrix, 0, sizeof(adjacencyMatrix));
12      
13        // Build the graph: populate degree array and adjacency matrix
14        for (auto& road : roads) {
15            int city1 = road[0];
16            int city2 = road[1];
17          
18            // Mark cities as connected in the adjacency matrix (bidirectional)
19            adjacencyMatrix[city1][city2] = 1;
20            adjacencyMatrix[city2][city1] = 1;
21          
22            // Increment degree count for both cities
23            ++degree[city1];
24            ++degree[city2];
25        }
26      
27        int maxRank = 0;
28      
29        // Check all pairs of cities to find the maximum network rank
30        for (int i = 0; i < n; ++i) {
31            for (int j = i + 1; j < n; ++j) {
32                // Network rank = sum of degrees minus shared edge (if exists)
33                // If cities i and j are connected, we subtract 1 to avoid counting the shared road twice
34                int currentRank = degree[i] + degree[j] - adjacencyMatrix[i][j];
35                maxRank = max(maxRank, currentRank);
36            }
37        }
38      
39        return maxRank;
40    }
41};
42
1/**
2 * Calculates the maximal network rank of an infrastructure network.
3 * The network rank of two cities is the total number of roads connected to either city.
4 * If a road directly connects the two cities, it is only counted once.
5 * 
6 * @param n - The number of cities in the network
7 * @param roads - Array of road connections, where each road is [cityA, cityB]
8 * @returns The maximum network rank among all pairs of different cities
9 */
10function maximalNetworkRank(n: number, roads: number[][]): number {
11    // Create adjacency matrix to track direct connections between cities
12    const adjacencyMatrix: number[][] = Array.from(
13        { length: n }, 
14        () => Array(n).fill(0)
15    );
16  
17    // Track the degree (number of connected roads) for each city
18    const degreeCount: number[] = Array(n).fill(0);
19  
20    // Build the graph by populating adjacency matrix and degree counts
21    for (const [cityA, cityB] of roads) {
22        adjacencyMatrix[cityA][cityB] = 1;
23        adjacencyMatrix[cityB][cityA] = 1;
24        degreeCount[cityA]++;
25        degreeCount[cityB]++;
26    }
27  
28    let maxRank = 0;
29  
30    // Check all pairs of cities to find the maximum network rank
31    for (let cityA = 0; cityA < n; cityA++) {
32        for (let cityB = cityA + 1; cityB < n; cityB++) {
33            // Calculate network rank: sum of degrees minus direct connection (if exists)
34            // If cities are directly connected, subtract 1 to avoid double counting
35            const currentRank = degreeCount[cityA] + degreeCount[cityB] - adjacencyMatrix[cityA][cityB];
36            maxRank = Math.max(maxRank, currentRank);
37        }
38    }
39  
40    return maxRank;
41}
42

Time and Space Complexity

Time Complexity: O(n^2 + m) where n is the number of cities and m is the number of roads.

  • Building the graph takes O(m) time as we iterate through all roads once
  • The nested loops iterate through all pairs of cities, which gives us O(n^2) iterations
  • For each pair, we perform constant time operations: checking set membership (a in g[b]) and calculating the sum of degrees
  • Therefore, the overall time complexity is O(m + n^2) = O(n^2) when simplified, since in the worst case m = O(n^2)

Space Complexity: O(n + m) which simplifies to O(n^2) in the worst case.

  • The graph g is stored as a dictionary of sets
  • In the worst case (complete graph), each city connects to all other cities
  • This means we store up to n sets, and each set can contain up to n-1 cities
  • Additionally, we store each edge twice (once for each direction in the adjacency sets)
  • Therefore, the space complexity is O(n + 2m) = O(n + m), which becomes O(n^2) when m = O(n^2) in a dense graph

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

Common Pitfalls

1. Double-Counting City Pairs

A frequent mistake is using two complete nested loops for a in range(n): for b in range(n): without proper filtering. This leads to:

  • Checking the same pair twice (e.g., (0,1) and (1,0))
  • Comparing a city with itself (e.g., (0,0))

Incorrect approach:

for city_a in range(n):
    for city_b in range(n):  # Wrong: includes duplicates and self-pairs
        if city_a != city_b:  # Still processes (a,b) and (b,a)
            rank = len(graph[city_a]) + len(graph[city_b]) - (city_a in graph[city_b])

Correct approach:

for city_a in range(n):
    for city_b in range(city_a + 1, n):  # Ensures each pair is checked only once
        rank = len(graph[city_a]) + len(graph[city_b]) - (city_a in graph[city_b])

2. Using Lists Instead of Sets for Adjacency Storage

Storing connections as lists rather than sets creates performance issues:

  • Checking if two cities are connected becomes O(degree) instead of O(1)
  • Can accidentally add duplicate connections

Problematic implementation:

graph = defaultdict(list)  # Using list instead of set
for a, b in roads:
    graph[a].append(b)
    graph[b].append(a)

# Later checking connection is O(degree) operation
if city_b in graph[city_a]:  # Linear search through list
    current_rank -= 1

Optimal implementation:

graph = defaultdict(set)  # Using set for O(1) lookups
for a, b in roads:
    graph[a].add(b)
    graph[b].add(a)

3. Forgetting to Handle Isolated Cities

When using defaultdict, isolated cities (with no roads) are handled automatically. However, if using a regular dictionary, you might encounter KeyError:

Error-prone code:

graph = {}
for a, b in roads:
    if a not in graph:
        graph[a] = set()
    if b not in graph:
        graph[b] = set()
    graph[a].add(b)
    graph[b].add(a)

# This fails if a city has no roads
for city_a in range(n):
    for city_b in range(city_a + 1, n):
        rank = len(graph[city_a]) + len(graph[city_b])  # KeyError if city not in graph

Safe approach:

# Either use defaultdict or handle missing keys explicitly
rank = len(graph.get(city_a, set())) + len(graph.get(city_b, set()))

4. Incorrect Subtraction Logic for Connected Cities

A subtle but critical error is forgetting to check if cities are connected or incorrectly implementing the check:

Wrong implementations:

# Forgot to subtract shared road
rank = len(graph[city_a]) + len(graph[city_b])

# Always subtracting 1
rank = len(graph[city_a]) + len(graph[city_b]) - 1

# Checking wrong direction (though this works with bidirectional graph)
rank = len(graph[city_a]) + len(graph[city_b]) - (city_b in graph[city_a])

The boolean expression (city_a in graph[city_b]) elegantly converts to 1 or 0, making the subtraction automatic and correct.

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

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

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

Load More