Number Of Provinces – 547. LeetCode

The Number of Provinces problem is a foundational graph theory challenge that involves finding the number of connected components in an undirected graph. This problem helps build a solid understanding of graph traversal techniques like Depth-First Search (DFS), Breadth-First Search (BFS), and Union-Find. Let’s dive into the problem statement and its key concepts.

Understanding the Problem: Number of Provinces

You are given:

• A matrix isConnected, where:

• isConnected[i][j] = 1 means there’s a direct connection between city i and city j.

• isConnected[i][j] = 0 means there’s no direct connection.

• Each city is connected to itself, meaning isConnected[i][i] = 1.

Goal:

Determine the number of provinces (connected components) in the graph.

Key Points

1. Provinces:

• A province is a group of cities that are directly or indirectly connected.

• For example:

• If city A is connected to city B, and city B is connected to city C, then A, B, and C form a single province.

2. Graph Representation:

• The adjacency matrix isConnected represents an undirected graph, where:

• Rows and columns correspond to cities (nodes).

• A value of 1 at isConnected[i][j] indicates an edge between nodes i and j.

3. Connected Components:

• The task is equivalent to finding connected components in an undirected graph.

• Each connected component represents one province.

Approach: Depth-First Search (DFS)

Key Intuition

• Each city represents a node, and each direct connection (1 in the matrix) represents an undirected edge.

• DFS allows us to traverse all nodes connected to a given starting node. Once a connected component is fully traversed, we can count it as one province.

Steps to Solve with DFS

1. Track Visited Nodes:

• Use a boolean array visited to keep track of which cities have been processed.

2. Traverse the Graph:

• For each unvisited city, initiate a DFS to visit all directly and indirectly connected cities.

3. Count Provinces:

• Every time a DFS starts from an unvisited city, increment the province count.

public class NumberOfProvinces {

    public int findCircleNum(int[][] isConnected) {
        int n = isConnected.length; // Number of cities
        boolean[] visited = new boolean[n]; // Track visited cities
        int provinces = 0; // Count of provinces

        // Iterate through each city
        for (int i = 0; i < n; i++) {
            if (!visited[i]) { // If city is unvisited, start a new DFS
                dfs(isConnected, visited, i);
                provinces++; // Increment province count
            }
        }

        return provinces;
    }

    private void dfs(int[][] isConnected, boolean[] visited, int city) {
        visited[city] = true; // Mark the city as visited

        // Explore all connected cities
        for (int neighbor = 0; neighbor < isConnected.length; neighbor++) {
            if (isConnected[city][neighbor] == 1 && !visited[neighbor]) {
                dfs(isConnected, visited, neighbor); // Recursively visit unvisited neighbors
            }
        }
    }
}

Approach: BFS

Steps to Solve with BFS

1. Initialize Variables:

• Use a boolean array visited[] to track whether a city has already been processed.

• Maintain a counter provinces to count the connected components.

2. Iterate Through Cities:

• For each city, if it hasn’t been visited, initiate a BFS traversal from that city.

• Mark all cities connected to it as visited.

3. Queue for BFS:

• Use a queue to process cities iteratively:

• Add the starting city to the queue.

• For each city in the queue, explore its unvisited neighbors and add them to the queue.

4. Count Provinces:

• Increment the provinces counter each time a BFS starts from an unvisited city.

import java.util.*;

public class NumberOfProvinces {

    public int findCircleNum(int[][] isConnected) {
        int n = isConnected.length; // Number of cities
        boolean[] visited = new boolean[n]; // Track visited cities
        int provinces = 0; // Count of provinces

        // Iterate through each city
        for (int i = 0; i < n; i++) {
            if (!visited[i]) { // If city is unvisited, start a new BFS
                bfs(isConnected, visited, i);
                provinces++; // Increment province count
            }
        }

        return provinces;
    }

    private void bfs(int[][] isConnected, boolean[] visited, int city) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(city); // Start BFS with the given city
        visited[city] = true; // Mark the city as visited

        while (!queue.isEmpty()) {
            int current = queue.poll();

            // Explore all connected cities
            for (int neighbor = 0; neighbor < isConnected.length; neighbor++) {
                if (isConnected[current][neighbor] == 1 && !visited[neighbor]) {
                    queue.add(neighbor); // Add unvisited neighbors to the queue
                    visited[neighbor] = true; // Mark neighbor as visited
                }
            }
        }
    }
}

Solving the Number of Provinces Problem with Disjoint Set (Union-Find)

Why Use Union-Find?

Union-Find is an efficient data structure for determining and managing connected components in a graph. It provides:

1. Efficient Connected Component Detection: Using union and find operations, you can group nodes into components efficiently.

2. Cycle Detection: In undirected graphs, Union-Find can detect cycles.

3. Space Efficiency: Union-Find uses minimal memory compared to BFS or DFS for large graphs.

Steps

1. Initialize Union-Find:

• Create a parent array where each city is its own parent initially.

• Optionally, maintain a rank array for union by rank optimization.

2. Process the Connections:

• Traverse the adjacency matrix isConnected.

• For each connection isConnected[i][j] = 1, perform a union operation to group the cities.

3. Count Provinces:

• After processing all connections, count the number of unique roots in the parent array. Each unique root represents one province.

public class NumberOfProvinces {

    public int findCircleNum(int[][] isConnected) {
        int n = isConnected.length; // Number of cities
        UnionFind uf = new UnionFind(n);

        // Process connections in the adjacency matrix
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (isConnected[i][j] == 1) {
                    uf.union(i, j); // Union the two cities
                }
            }
        }

        // Return the number of unique provinces (connected components)
        return uf.getNumberOfProvinces();
    }

    // Union-Find class
    class UnionFind {
        private int[] parent;
        private int[] rank;
        private int count; // Number of connected components

        public UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            count = n; // Initially, each node is its own component

            for (int i = 0; i < n; i++) {
                parent[i] = i; // Each city is its own parent initially
            }
        }

        // Find operation with path compression
        public int find(int x) {
            if (parent[x] != x) {
                parent[x] = find(parent[x]); // Path compression
            }
            return parent[x];
        }

        // Union operation with union by rank
        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);

            if (rootX != rootY) {
                if (rank[rootX] > rank[rootY]) {
                    parent[rootY] = rootX;
                } else if (rank[rootX] < rank[rootY]) {
                    parent[rootX] = rootY;
                } else {
                    parent[rootY] = rootX;
                    rank[rootX]++;
                }
                count--; // Reduce the number of components
            }
        }

        // Get the number of unique provinces (connected components)
        public int getNumberOfProvinces() {
            return count;
        }
    }
}

Conclusion

The Number of Provinces problem is a fundamental graph traversal challenge that reinforces core algorithmic concepts like connected components, DFS, BFS, and Union-Find. By mastering these techniques, you gain valuable tools for tackling a wide range of graph-based problems, from social networks to real-world logistics. Each approach—whether it’s the simplicity of DFS, the level-wise exploration of BFS, or the efficiency of Union-Find—offers unique advantages, making them indispensable for different scenarios.

Leave a Reply

Your email address will not be published. Required fields are marked *