Meta Data#
Difficulty: medium First Attempt: 2025-05-04
- Total time: 10:00.00
Intuition#
Classic Union-Find problem. At first, I didn’t realize that I could use the length of isConnected to initialize the parent and rank arrays. I mistakenly thought I wouldn’t know the size of the nodes until I iterated through the isConnected array. Therefore, I created dictionaries for these two to record node information. After that, it became a standard Union-Find problem: just iterate through isConnected, perform union operations, and we’ll eventually obtain the connected groups.
Approach#
Union Find#
class Solution:
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        parents = {}
        rank = {}
        def find(x: int):
            root = parents.get(x, x)
            if root != x:
                parent = find(root)
                parents[x] = parent
                return parent  
            else:
                parents[x] = x
                return x
        def union(x, y):
            x_root = find(x)
            y_root = find(y)
            if x_root == y_root:
                return
            x_rank = rank.get(x_root, 1)
            y_rank = rank.get(y_root, 1)
            if x_rank > y_rank:
                rank[x_root] += y_rank
                parents[y_root] = x_root
            
            else:
                
                rank[y_root] = rank.get(y_root, 1) + x_rank
                parents[x_root] = y_root
        for i in range(len(isConnected)):
            for j in range(len(isConnected[0])):
                if isConnected[i][j]:
                    union(i, j)
        return len(set(find(i) for i in range(len(isConnected))))
Union Find w/ known the size#
class Solution:
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        n = len(isConnected)
        parents = [i for i in range(n)]
        rank = [1 for _ in range(n)]
        def find(x: int):
            root = parents[x]
            if root != x:
                parent = find(root)
                parents[x] = parent
                return parent
            else:
                parents[x] = x
                return x
        def union(x, y):
            x_root = find(x)
            y_root = find(y)
            if x_root == y_root:
                return
            x_rank = rank[x_root]
            y_rank = rank[y_root]
            if x_rank > y_rank:
                rank[x_root] += y_rank
                parents[y_root] = x_root
            
            else:
                
                rank[y_root] = rank[y_root] + x_rank
                parents[x_root] = y_root
        for i in range(len(isConnected)):
            for j in range(len(isConnected[0])):
                if isConnected[i][j]:
                    union(i, j)
        return len(set(find(i) for i in range(n)))
DFS#
class Solution:
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        n = len(isConnected)
        seen = set()
        ans = 0
        def dfs(x):
            for idx in range(n):
                if not isConnected[x][idx] or idx in seen:
                    continue
                seen.add(idx)
                dfs(idx)
        for idx in range(n):
            if idx not in seen:
                ans += 1
                dfs(idx)
        return ans
Findings#
Encountered Problems#
Initially, I used the following function to get the size of the result set:
return len(set(parents.values()))
However, the blind spot here is that after iterating through all the connections, this approach doesn’t ensure that every node in parents is pointing to its final root node. For example:
If the connections are:
- a ↔ b
- c ↔ d
- d ↔ e
- e ↔ b
When we process the first connection, we might assign the root of a to b. But after processing the last connection, the root of b changes to e. Using parents.values() directly at this stage would lead to incorrect results. Therefore, we must call find() on each node to retrieve its true representative root.

