Leetcode 547. Friend Circles

Problem Explanation:

A friendship matrix M of student relations is given, where N rows represent each student's friendship relation to the other N students. If M[i][j] = 1, then the ith and jth students are direct friends with each other, otherwise they are not. The problem asks us to find the total number of friend circles, where a friend circle is a group of students who are directly or indirectly connected.

Let's walk through the first example:

1Input:
2[[1,1,0],
3 [1,1,0],
4 [0,0,1]]
5Output: 2

Here, students 0 and 1 are direct friends as represented by the 1 in the intersections matrices M[0][1] and M[1][0]. The third student is not connected to anyone, so it forms its own friend circle.

Approach Used in the Solution:

The provided solution uses the approach of Disjoint Set / Union-Find data structure, which is an efficient structure to solve connectivity problems. The Union Find has union() to connect two elements and find() to check if two elements are in the same set. Initially, each student is considered its own set or friend circle. When a direct friendship is found, we connect the two circles into one by union operation. The count number inside the structure represents the number of disjoint sets.

Steps of the Algorithm:

Let's walk through these steps with an example:

1Input:
2[[1,1,0],
3 [1,1,1],
4 [0,1,1]]
5Output: 1
  1. Initialize UnionFind with N friend circles.

  2. Traverse the matrix, if a friendship relation M[i][j] equals 1, apply union operation to ith and jth friend circles. For example, M[0][1] and M[1][2] both equal to 1, so apply the union operation on friend circles of student 0,1 and 1,2 respectively.

  3. The final count of disjointsets in the UnionFind structure represents the total friend circle count, which is 1 in this example.

Now, let's present this solution in Python, Java, JavaScript, C++, and C#.

Python Solution:

1
2python
3class UnionFind:
4    def __init__(self, n):
5        self.count = n
6        self.parent = list(range(n))
7
8    def union(self, i, j):
9        root_i = self.find(i)
10        root_j = self.find(j)
11        if root_i != root_j:
12            self.parent[root_j] = root_i
13            self.count -= 1
14
15    def find(self, i):
16        if self.parent[i] != i:
17            self.parent[i] = self.find(self.parent[i])
18        return self.parent[i]
19
20class Solution:
21    def findCircleNum(self, M):
22        if not M: return 0
23        n = len(M)
24        uf = UnionFind(n)
25        for i in range(n):
26            for j in range(i, n):
27                if M[i][j] == 1:
28                    uf.union(i, j)
29        return uf.count

Java Solution:

1
2java
3class Solution {
4    public int findCircleNum(int[][] M) {
5        int N = M.length;
6        int[] parent = new int[N];
7        for (int i = 0; i < N; i++) parent[i] = i;
8        int count = N;
9        for (int i = 0; i < N; i++) {
10            for (int j = i + 1; j < N; j++) {
11                if (M[i][j] == 1) {
12                    int rootI = find(i, parent), rootJ = find(j, parent);
13                    if (rootI != rootJ) {
14                        parent[rootJ] = rootI;  
15                        count--;
16                    }
17                }
18            }
19        }
20        return count;
21    }
22    
23    private int find(int i, int[] parent) {
24        if (parent[i] == i) return i;
25        return parent[i] = find(parent[i], parent);
26    }
27}

JavaScript Solution:

1
2javascript
3var findCircleNum = function(M) {
4    let N = M.length;
5    let parents = []; 
6
7    for(let i = 0; i < N; i++) {
8        parents[i] = i; 
9    }
10
11    let count = N;
12
13    for(let i = 0; i < N; i++) {
14        for(let j = i+1; j < N; j++) {
15            if(M[i][j] == 1) {
16                let rootI = find(i);
17                let rootJ = find(j);
18
19                if(rootI != rootJ) {
20                    parents[rootJ] = rootI; 
21                    count--;
22                }
23            }
24        }
25    }
26
27    return count;
28
29    function find(i) {
30        if(parents[i] == i) return i;
31        return parents[i] = find(parents[i]);
32    }
33}

C++ Solution:

1
2c++
3class Solution {
4public:
5    vector<int> parent;
6    int find(int i) { 
7        if (parent[i] == i) return i; 
8        return parent[i] = find(parent[i]); 
9    }
10    
11    int findCircleNum(vector<vector<int>>& M) {
12        int n = M.size();
13        parent = vector<int>(n);
14        for(int i = 0; i < n; i++) parent[i] = i;
15        int count = n;
16        for(int i = 0; i < n; i++) {
17            for(int j = i + 1; j < n; j++) {
18                if (M[i][j] == 1) {
19                    int root_i = find(i), root_j = find(j);
20                    if(root_i != root_j) {
21                        parent[root_j] = root_i;
22                        count--;
23                    }
24                }
25            }
26        }
27        return count;
28    }
29};

C# Solution:

1
2csharp
3public class Solution {
4    public int FindCircleNum(int[][] M) {
5        int N = M.Length;
6        int[] parent = new int[N];
7        for (int i = 0; i < N; i++) parent[i] = i;
8        int count = N;
9        for (int i = 0; i < N; i++) {
10            for (int j = i + 1; j < N; j++) {
11                if (M[i][j] == 1) {
12                    int rootI = Find(i, parent), rootJ = Find(j, parent);
13                    if (rootI != rootJ) {
14                        parent[rootJ] = rootI;  
15                        count--;
16                    }
17                }
18            }
19        }
20        return count;
21    }
22    
23    private int Find(int i, int[] parent) {
24        if (parent[i] == i) return i;
25        return parent[i] = Find(parent[i], parent);
26    }
27}

In all these solutions, we use the Union-Find data structure and its union and find operations to calculate the total number of disjoint sets which represent the friend circles. The time complexity of the solution is O(N^3) because of the union operation inside double loops, which is acceptable as N is up to 200.The above solutions are complete and optimal. Python, JavaScript, Java, C++ and C# solutions were provided. The solutions implement a Union-Find or Disjoint Set data structure which allows us to efficiently solve connectivity problems such as the friend circles problem.

The time complexity for the provided solutions is on the order of O(N^3), which is acceptable given the problem constraints (N <= 200). The space complexity is O(N), since we create a parent array or list to store the parent of each student.

To delve a bit further into the time complexity: in worst case scenario, for each i and j, we have to perform a union operation which could take O(N) time. As this operation is enclosed in a double loop, it results in being an O(N^3) time complexity. However, keep in note that this worst case scenario would rarely happen and mostly the union operation performs in nearly constant time due to path compression and rank optimization.

On the other hand, the space complexity of O(N) arises from the parent array or list. It is used to store the parent of each student and hence its size is proportional to the number of students.

To further optimize these solutions, we could apply a depth-first search or breadth-first search algorithm. This could potentially make the solution more efficient as it might not require the use of the double loop. However, these will just be minor improvements and the primary concept to solve the problem would remain the basis of a UnionFind data structure to check connectivity between nodes.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫