Taro Logo

Number of Provinces

Medium
Amazon logo
Amazon
3 views
Topics:
GraphsArrays

You are given an n x n matrix isConnected where isConnected[i][j] = 1 if the i^th city and the j^th city are directly connected, and isConnected[i][j] = 0 otherwise.

A province is a group of directly or indirectly connected cities and no other cities outside of the group.

Write a function to return the total number of provinces.

Example 1:

isConnected = [[1,1,0],[1,1,0],[0,0,1]]

Output: 2

Example 2:

isConnected = [[1,0,0],[0,1,0],[0,0,1]]

Output: 3

Constraints:

  • 1 <= n <= 200
  • n == isConnected.length
  • n == isConnected[i].length
  • isConnected[i][j] is 1 or 0.
  • isConnected[i][i] == 1
  • isConnected[i][j] == isConnected[j][i]

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What is the data type of the input array representing the adjacency matrix? Is it an array of integers, booleans, or something else?
  2. What is the maximum possible size of the input matrix (number of cities/nodes)?
  3. Is the input matrix guaranteed to be square, representing a valid adjacency matrix (i.e., is the number of rows equal to the number of columns)?
  4. Is the adjacency matrix symmetric? If city 'i' is connected to city 'j', is city 'j' also guaranteed to be connected to city 'i'?
  5. Does the connection include the city itself, meaning matrix[i][i] will be 1 (or true), or should I assume cities cannot be connected to themselves directly?

Brute Force Solution

Approach

Think of each city as a person and a connection as friendship. The brute force approach explores how many separate groups of friends (provinces) there are by directly examining every possible friendship between cities.

Here's how the algorithm would work step-by-step:

  1. Start with the first city and mark it as visited.
  2. Look at all other cities to see if they are 'friends' with the first city.
  3. If a city is a 'friend', mark that city as visited as well.
  4. Repeat the previous step, looking at all 'friends' of the first city's friends, and mark them as visited, continuing to find all cities connected to the initial city.
  5. Once we've found all the 'friends' connected to the first city, move to the next city that hasn't been visited.
  6. Repeat the same process of finding all 'friends' of this new city and marking them as visited.
  7. Keep doing this until all cities have been visited.
  8. The number of times we had to start a new search for unvisited cities is the number of separate groups of friends, or 'provinces'.

Code Implementation

def find_number_of_provinces(adjacency_matrix):
    number_of_cities = len(adjacency_matrix)
    visited = [False] * number_of_cities
    number_of_provinces = 0

    def depth_first_search(city_index):
        visited[city_index] = True

        # Explore all connected cities using DFS
        for neighbor_index in range(number_of_cities):
            if adjacency_matrix[city_index][neighbor_index] == 1 and not visited[neighbor_index]:

                # Recursively visit unvisited neighbors
                depth_first_search(neighbor_index)

    for city_index in range(number_of_cities):

        # Start DFS if city not visited yet
        if not visited[city_index]:

            # Increment province count on new component
            number_of_provinces += 1
            depth_first_search(city_index)

    return number_of_provinces

Big(O) Analysis

Time Complexity
O(n²)The algorithm iterates through each of the n cities. For each city, it explores its connections to other cities, which can involve checking up to n-1 other cities in the worst case to find all connected components. Thus, in the worst-case scenario, where we might have to check every possible connection between cities, the algorithm performs approximately n * n operations. This leads to a time complexity of O(n²).
Space Complexity
O(N)The algorithm uses a 'visited' array to keep track of which cities have already been explored, as described in steps 1-7. This array needs to store a boolean value for each of the N cities. Therefore, the auxiliary space required for the visited array grows linearly with the number of cities, N. Hence, the space complexity is O(N).

Optimal Solution

Approach

The problem involves finding groups of connected cities, where a connection means there's a direct road between them. The most efficient way to do this is to explore each city and its connections, marking all connected cities as part of the same group until no more connections are found.

Here's how the algorithm would work step-by-step:

  1. Imagine each city as a separate, unvisited place.
  2. Start with any unvisited city.
  3. Look at all the cities directly connected to this city.
  4. Mark all connected cities as visited, indicating they belong to the same group.
  5. For each of these newly visited cities, check their connections and repeat the process of marking connected cities as visited until you can't find any more.
  6. Once you've explored all connections for a city and its connected group, and there are still unvisited cities, start the process again with another unvisited city. Each time you complete this process, you've found another distinct group of connected cities.
  7. The total number of times you start the process with a new, unvisited city is the number of provinces (or separate groups of connected cities).

Code Implementation

def find_number_of_provinces(adjacency_matrix):
    number_of_cities = len(adjacency_matrix)
    visited_cities = [False] * number_of_cities
    number_of_provinces = 0

    def depth_first_search(city_index):
        visited_cities[city_index] = True
        for neighbor_index in range(number_of_cities):
            if adjacency_matrix[city_index][neighbor_index] == 1 and not visited_cities[neighbor_index]:
                depth_first_search(neighbor_index)

    for city_index in range(number_of_cities):
        # Start a new DFS if the city hasn't been visited.
        if not visited_cities[city_index]:

            depth_first_search(city_index)

            # Increment province count after exploring all connected cities.
            number_of_provinces += 1

    return number_of_provinces

Big(O) Analysis

Time Complexity
O(n²)The algorithm iterates through each city to find connected components. For each city, it potentially explores all other cities to check for connections. In the worst case, every city is connected to every other city, requiring us to traverse the entire adjacency matrix. This involves checking each of the n cities against every other of the n cities, therefore, the time complexity is O(n²).
Space Complexity
O(N)The algorithm uses a 'visited' array to keep track of which cities have already been explored, as described in steps 1-6. This array has a size equal to the number of cities, N. Since the algorithm explores each city and its connections using this 'visited' array, the space required grows linearly with the number of cities. Therefore, the auxiliary space complexity is O(N).

Edge Cases

CaseHow to Handle
Null or empty input adjacency matrixReturn 0 if the matrix is null or empty since there are no cities or provinces.
Adjacency matrix is not square (n x n)Throw an IllegalArgumentException or return an error code since a non-square matrix is invalid for representing city connections.
Single city (n=1)Return 1, as a single city always forms its own province.
All cities are disconnected (all values are 0 except the diagonal)Return n (number of cities) since each city forms its own province.
All cities are connected (all values are 1)Return 1, since all cities form a single province.
Large number of cities (scalability)Use a Disjoint Set Union (DSU) or Depth-First Search (DFS) algorithm, which can handle large inputs efficiently in O(n) or O(n^2) time respectively.
Input matrix contains values other than 0 or 1Treat any non-zero value as a connection (1) or throw an exception since the problem usually defines 0 and 1 to represent connectedness.
Integer overflow in the number of provinces (highly unlikely but theoretically possible with extremely large n)The number of provinces will always be less than or equal to n, so integer overflow is not expected given reasonable input size, but one could check for it in extremely high use cases.