Skip to content

195. Number of Provinces

๐Ÿ”— LeetCode Problem: 547. Number of Provinces
๐Ÿ“Š Difficulty: Medium
๐Ÿท๏ธ Topics: Graph, DFS, BFS, Union-Find, Connected Components

Problem Statement

There are n cities. Some of them are connected, while some are not. If city a is connected directly with city b, and city b is connected directly with city c, then city a is connected indirectly with city c.

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

You are given an n x n matrix isConnected where isConnected[i][j] = 1 if the iแต—สฐ city and the jแต—สฐ city are directly connected, and isConnected[i][j] = 0 otherwise.

Return the total number of provinces.

Example 1:

Input: isConnected = [[1,1,0],
                      [1,1,0],
                      [0,0,1]]

Output: 2

Explanation:
  Cities 0 and 1 are connected: 0 โ†” 1
  City 2 is alone: 2

  Province 1: {0, 1}
  Province 2: {2}

  Total: 2 provinces

Example 2:

Input: isConnected = [[1,0,0],
                      [0,1,0],
                      [0,0,1]]

Output: 3

Explanation:
  No cities are connected.

  Province 1: {0}
  Province 2: {1}
  Province 3: {2}

  Total: 3 provinces (all isolated)

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]


๐ŸŒณ Visual Understanding - The Complete Picture

The Problem We're Solving ๐Ÿค”

Given connections between cities, find number of groups: ๐Ÿ™๏ธ

Example:
  Cities: 0, 1, 2, 3
  Connections: 0โ†”1, 2โ†”3

  Groups (Provinces):
    Group 1: {0, 1}  (connected to each other)
    Group 2: {2, 3}  (connected to each other)

  Answer: 2 provinces

Key question: How many separate groups of connected cities? ๐ŸŽฏ

Understanding the Matrix ๐Ÿ“Š

isConnected matrix: shows direct connections

Example:
     0  1  2
  0 [1, 1, 0]    City 0: connected to 0 (itself), 1
  1 [1, 1, 0]    City 1: connected to 0, 1 (itself)
  2 [0, 0, 1]    City 2: connected to 2 (itself only)

Visual:
  0 โ†” 1    2

Two provinces! โœ…
Another example:
     0  1  2  3
  0 [1, 1, 0, 0]
  1 [1, 1, 1, 0]
  2 [0, 1, 1, 0]
  3 [0, 0, 0, 1]

Connections:
  0โ†”1, 1โ†”2  (0,1,2 form one group)
  3 alone   (3 is isolated)

Visual:
  0 โ†” 1 โ†” 2    3

Two provinces: {0,1,2} and {3} โœ…

๐Ÿง  The Journey - From Real World to Union-Find

Part 1: The Real World Problem - "Friend Groups" ๐Ÿ‘ฅ

๐Ÿ’ญ Imagine a classroom with students:

Students: Alice, Bob, Carol, Dave, Eve

Friendships:
  Alice โ†” Bob
  Carol โ†” Dave
  Eve is alone

Question: How many friend groups?

Groups:
  Group 1: {Alice, Bob}
  Group 2: {Carol, Dave}
  Group 3: {Eve}

Answer: 3 friend groups! โœ…

This is EXACTLY our provinces problem! ๐ŸŽฏ
Just replace "students" with "cities"
and "friendships" with "connections"!

Part 2: The Naive Way - "Ask Everyone!" ๐Ÿค”

๐Ÿ’ญ How would you count friend groups in real life?

Approach 1: Start with someone, ask who their friends are

Start with Alice:
  "Who are your friends?"
  Alice: "Bob!"

  "Bob, who are your friends?"
  Bob: "Alice!"

  Group 1 complete: {Alice, Bob} โœ…

Start with Carol (unvisited):
  "Who are your friends?"
  Carol: "Dave!"

  "Dave, who are your friends?"
  Dave: "Carol!"

  Group 2 complete: {Carol, Dave} โœ…

Start with Eve (unvisited):
  "Who are your friends?"
  Eve: "Nobody!"

  Group 3 complete: {Eve} โœ…

Total: 3 groups!

This is DFS/BFS approach! ๐ŸŒŠ

Part 3: The Smart Way - "Union-Find Intro" ๐Ÿ’ก

๐Ÿ’ญ But there's a SMARTER way using Union-Find!

Real world analogy: School clubs! ๐Ÿซ

Initially, everyone is their own club:
  Alice's club: {Alice}
  Bob's club: {Bob}
  Carol's club: {Carol}
  Dave's club: {Dave}
  Eve's club: {Eve}

  Total clubs: 5 โœ…

Now, process friendships:

Friendship: Alice โ†” Bob
  Action: MERGE their clubs!
  Result:
    AliceBob's club: {Alice, Bob}
    Carol's club: {Carol}
    Dave's club: {Dave}
    Eve's club: {Eve}

  Total clubs: 4 โœ…

Friendship: Carol โ†” Dave
  Action: MERGE their clubs!
  Result:
    AliceBob's club: {Alice, Bob}
    CarolDave's club: {Carol, Dave}
    Eve's club: {Eve}

  Total clubs: 3 โœ…

No more friendships!
Final answer: 3 groups! ๐ŸŽฏ

This is Union-Find! ๐ŸŒŸ

Part 4: Union-Find Core Concepts ๐Ÿ”‘

โœจ Union-Find has THREE key operations:

1๏ธโƒฃ MAKE-SET: Create individual sets
   Initially, everyone is their own set

2๏ธโƒฃ FIND: Find which set someone belongs to
   "Who is the leader of your group?"

3๏ธโƒฃ UNION: Merge two sets together
   "Join these two groups into one!"

Data structure: Array of parents! ๐Ÿ“Š

Example with 5 people (indices 0-4):
  parent = [0, 1, 2, 3, 4]

  Meaning: Each person is their own parent (own leader)

  Person 0: parent[0] = 0 (leader of self)
  Person 1: parent[1] = 1 (leader of self)
  Person 2: parent[2] = 2 (leader of self)
  Person 3: parent[3] = 3 (leader of self)
  Person 4: parent[4] = 4 (leader of self)

After union(0, 1):
  parent = [0, 0, 2, 3, 4]
         //  โ†‘
  Person 1 now points to 0 as leader!

  Group: {0, 1} with leader 0

After union(2, 3):
  parent = [0, 0, 2, 2, 4]
              //    โ†‘
  Person 3 now points to 2 as leader!

  Groups: {0, 1} with leader 0
          {2, 3} with leader 2
          {4}    with leader 4

  Total groups: 3 โœ…

Count groups: Count how many are their own parent!
  parent[0] == 0? YES โœ… (leader)
  parent[1] == 1? NO  โŒ
  parent[2] == 2? YES โœ… (leader)
  parent[3] == 3? NO  โŒ
  parent[4] == 4? YES โœ… (leader)

  Leaders: 3 โ†’ Answer: 3 groups! ๐ŸŽฏ

Part 5: Union-Find Operations in Detail ๐Ÿ”

๐Ÿ’ญ Let's understand FIND and UNION deeply:

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘         FIND: "Who's your leader?"            โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

find(x):
  Keep following parent pointers until you find the leader!
  Leader = someone who is their own parent

Example:
  parent = [0, 0, 1, 2]

  find(0):
    parent[0] = 0
    0 == 0? YES! Leader found! โœ…
    Return: 0

  find(1):
    parent[1] = 0
    1 == 0? NO
    Follow: go to 0
    parent[0] = 0
    0 == 0? YES! Leader found! โœ…
    Return: 0

  find(2):
    parent[2] = 1
    2 == 1? NO
    Follow: go to 1
    parent[1] = 0
    1 == 0? NO
    Follow: go to 0
    parent[0] = 0
    0 == 0? YES! Leader found! โœ…
    Return: 0

  find(3):
    parent[3] = 2
    Follow chain: 3 โ†’ 2 โ†’ 1 โ†’ 0 โœ…
    Return: 0

All point to same leader 0!
They're in the same group! ๐ŸŽฏ

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘        UNION: "Merge two groups!"             โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

union(x, y):
  1. Find leader of x's group
  2. Find leader of y's group
  3. Make one leader point to the other!

Example:
  parent = [0, 1, 2]  (3 separate groups)

  union(0, 1):
    leader_0 = find(0) = 0
    leader_1 = find(1) = 1
    Make 1 point to 0:
    parent[1] = 0

    Result: parent = [0, 0, 2]
    Groups: {0, 1} and {2}

  union(1, 2):
    leader_1 = find(1) = 0  (1's leader is 0!)
    leader_2 = find(2) = 2
    Make 2 point to 0:
    parent[2] = 0

    Result: parent = [0, 0, 0]
    Groups: {0, 1, 2} - ALL CONNECTED! โœ…

Beautiful! ๐ŸŒŸ

Part 6: Why Union-Find is Powerful ๐Ÿ’ช

๐Ÿ’ญ Comparing approaches:

DFS/BFS Approach:
  โœ… Easy to understand
  โœ… Works perfectly
  โŒ Builds graph first
  โŒ Visits every node/edge
  Time: O(nยฒ) for this problem

Union-Find Approach:
  โœ… Direct: process connections as we see them
  โœ… No graph needed!
  โœ… Efficient with optimizations
  โœ… Answers "are these connected?" in O(1) amortized!
  Time: O(nยฒ) but with TINY constant factor

Real power shows in DYNAMIC scenarios:
  "Are cities X and Y connected?"
  Union-Find: O(1) amortized! โšก
  DFS/BFS: O(n) every time! โŒ

For this problem, both work!
But Union-Find teaches fundamental pattern! ๐ŸŽฏ

๐ŸŽฏ Approach 1: DFS (Easiest to Understand!)

The Key Insight ๐Ÿ’ก

Use DFS to find connected components ๐ŸŒŠ. Treat matrix as graph: if isConnected[i][j] = 1, there's edge iโ†”j ๐Ÿ”—. For each unvisited city, start DFS to mark all cities in same province ๐Ÿ“. Count how many DFS starts = number of provinces! ๐ŸŽฏ. Time: O(nยฒ) โšก, Space: O(n) ๐Ÿ“ฆ!

Classic connected components! โœจ

Complete Implementation

class Solution {
    public int findCircleNum(int[][] isConnected) {
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // DFS APPROACH - FIND CONNECTED COMPONENTS ๐ŸŒŠ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // Each DFS explores one complete province
        // Count number of DFS starts = number of provinces!
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

        int n = isConnected.length;
        boolean[] visited = new boolean[n];
        int provinces = 0;

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // TRY STARTING DFS FROM EACH CITY ๐Ÿš€
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        for (int i = 0; i < n; i++) {
            if (!visited[i]) {
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                // FOUND NEW PROVINCE! ๐ŸŽ‰
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                dfs(i, isConnected, visited);
                provinces++;
            }
        }

        return provinces;
    }

    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // DFS: MARK ALL CITIES IN SAME PROVINCE ๐Ÿ”
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    private void dfs(int city, int[][] isConnected, boolean[] visited) {
        visited[city] = true;

        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        // VISIT ALL CONNECTED CITIES ๐ŸŒŠ
        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        for (int other = 0; other < isConnected.length; other++) {
            if (isConnected[city][other] == 1 && !visited[other]) {
                dfs(other, isConnected, visited);
            }
        }
    }
}

Why This Works ๐ŸŽฏ

DFS explores entire province: ๐ŸŒŠ - Start from unvisited city - Visit all connected cities recursively - Marks entire province as visited!

Count DFS starts: ๐Ÿ“Š - Each DFS start = found new province - Already visited cities don't start new DFS - Total starts = total provinces!

Simple and intuitive: โœจ - Just connected components counting - Standard graph algorithm!


๐ŸŽฏ Approach 2: BFS (Also Easy!)

The Key Insight ๐Ÿ’ก

Use BFS for level-by-level exploration ๐ŸŒŠ. Same idea as DFS but with queue! ๐Ÿ”„ For each unvisited city, BFS marks all connected cities in same province ๐Ÿ“. Time: O(nยฒ) โšก, Space: O(n) ๐Ÿ“ฆ!

BFS alternative! ๐ŸŒŸ

Complete Implementation

class Solution {
    public int findCircleNum(int[][] isConnected) {
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // BFS APPROACH - FIND CONNECTED COMPONENTS ๐ŸŒŠ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

        int n = isConnected.length;
        boolean[] visited = new boolean[n];
        int provinces = 0;

        for (int i = 0; i < n; i++) {
            if (!visited[i]) {
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                // FOUND NEW PROVINCE! BFS to mark all ๐ŸŽ‰
                // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
                bfs(i, isConnected, visited);
                provinces++;
            }
        }

        return provinces;
    }

    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // BFS: EXPLORE PROVINCE LEVEL BY LEVEL ๐Ÿ”
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    private void bfs(int start, int[][] isConnected, boolean[] visited) {
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(start);
        visited[start] = true;

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

            // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
            // CHECK ALL POTENTIAL CONNECTIONS ๐Ÿ”—
            // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
            for (int other = 0; other < isConnected.length; other++) {
                if (isConnected[city][other] == 1 && !visited[other]) {
                    queue.offer(other);
                    visited[other] = true;
                }
            }
        }
    }
}

๐ŸŽฏ Approach 3: Union-Find (The New Pattern!)

The Key Insight ๐Ÿ’ก

Use Union-Find data structure for dynamic connectivity! ๐Ÿ”— Initialize each city as its own set ๐Ÿ—๏ธ. For each connection, union the two cities ๐Ÿค. Count final number of disjoint sets = provinces! ๐Ÿ“Š Time: O(nยฒ ร— ฮฑ(n)) where ฮฑ is inverse Ackermann (โ‰ˆ O(1)) โšก, Space: O(n) ๐Ÿ“ฆ!

Efficient and elegant! ๐ŸŒŸ

Complete Implementation

class Solution {
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // UNION-FIND DATA STRUCTURE ๐Ÿ”—
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    private int[] parent;
    private int[] rank;  // For union by rank optimization

    public int findCircleNum(int[][] isConnected) {
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // UNION-FIND APPROACH ๐ŸŒŸ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // Process connections, union cities
        // Count disjoint sets at the end!
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

        int n = isConnected.length;
        parent = new int[n];
        rank = new int[n];

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // INITIALIZE: Each city is its own set ๐Ÿ—๏ธ
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        for (int i = 0; i < n; i++) {
            parent[i] = i;  // Each is its own parent (leader)
            rank[i] = 0;    // Initial rank is 0
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // PROCESS ALL CONNECTIONS ๐Ÿ”—
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {  // j > i to avoid duplicates
                if (isConnected[i][j] == 1) {
                    union(i, j);  // Merge their sets!
                }
            }
        }

        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // COUNT DISJOINT SETS ๐Ÿ“Š
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        int provinces = 0;
        for (int i = 0; i < n; i++) {
            if (parent[i] == i) {  // Is this a leader?
                provinces++;
            }
        }

        return provinces;
    }

    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // FIND: Get the leader (root) of the set ๐Ÿ”
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // With PATH COMPRESSION optimization! โšก
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    private int find(int x) {
        if (parent[x] != x) {
            // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
            // PATH COMPRESSION: Make x point directly to root
            // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // UNION: Merge two sets ๐Ÿค
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    // With UNION BY RANK optimization! โšก
    // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
    private void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if (rootX == rootY) {
            return;  // Already in same set!
        }

        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        // UNION BY RANK: Attach smaller tree under larger
        // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }
}

Why This Works ๐ŸŽฏ

Union-Find maintains disjoint sets: ๐Ÿ”— - Each city starts in its own set - Union merges sets when cities connected - Find tells us which set a city belongs to!

Path compression optimization: โšก - Makes trees flat - Future finds become O(1)! - parent[x] = find(parent[x])

Union by rank optimization: ๐Ÿ“Š - Keeps trees balanced - Attach smaller tree to larger - Prevents long chains!

Count leaders = count sets: ๐ŸŽฏ - Leaders: nodes where parent[i] == i - Each leader represents one province!


๐Ÿ” Detailed Dry Run - Union-Find Approach

Let's trace Union-Find on:

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

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                       INITIALIZATION                         โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

n = 3 cities (0, 1, 2)

Initialize parent and rank:
  parent = [0, 1, 2]  // Each city is its own parent
  rank   = [0, 0, 0]  // All ranks start at 0

Visualization:
  0    1    2    (3 separate sets)

Sets: {0}, {1}, {2}
Count: 3 provinces initially
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                   PROCESS CONNECTIONS                        โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ CHECK: isConnected[0][1] = 1                   โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘ Connection found: 0 โ†” 1                        โ•‘
โ•‘ Call: union(0, 1)                               โ•‘
โ•‘                                                 โ•‘
โ•‘ Step 1: Find leaders                            โ•‘
โ•‘   rootX = find(0) = 0                          โ•‘
โ•‘   rootY = find(1) = 1                          โ•‘
โ•‘                                                 โ•‘
โ•‘ Step 2: Compare ranks                           โ•‘
โ•‘   rank[0] = 0                                   โ•‘
โ•‘   rank[1] = 0                                   โ•‘
โ•‘   Equal ranks! Attach 1 to 0                    โ•‘
โ•‘                                                 โ•‘
โ•‘ Step 3: Union                                   โ•‘
โ•‘   parent[1] = 0                                โ•‘
โ•‘   rank[0]++  (0 โ†’ 1)                           โ•‘
โ•‘                                                 โ•‘
โ•‘ Result:                                         โ•‘
โ•‘   parent = [0, 0, 2]                           โ•‘
โ•‘   rank   = [1, 0, 0]                           โ•‘
โ•‘                                                 โ•‘
โ•‘ Visualization:                                  โ•‘
โ•‘     0                                           โ•‘
โ•‘     โ†‘                                           โ•‘
โ•‘     1        2                                  โ•‘
โ•‘                                                 โ•‘
โ•‘ Sets: {0, 1}, {2}                              โ•‘
โ•‘ Count: 2 provinces now!                         โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ CHECK: isConnected[0][2] = 0                   โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘ No connection! Skip!                            โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ CHECK: isConnected[1][2] = 0                   โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘ No connection! Skip!                            โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                    COUNT PROVINCES                           โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Final state:
  parent = [0, 0, 2]

Count leaders (parent[i] == i):
  parent[0] == 0? YES โœ… Leader found! Count = 1
  parent[1] == 1? NO  โŒ (parent[1] = 0)
  parent[2] == 2? YES โœ… Leader found! Count = 2

Total provinces: 2 โœ…
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                    FINAL RESULT: 2                           โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘                                                              โ•‘
โ•‘  Province 1: Cities {0, 1}  (leader: 0)                    โ•‘
โ•‘  Province 2: City {2}        (leader: 2)                    โ•‘
โ•‘                                                              โ•‘
โ•‘  Visual:                                                     โ•‘
โ•‘    0 โ†” 1      2                                             โ•‘
โ•‘                                                              โ•‘
โ•‘  Answer: 2 provinces! ๐ŸŽฏ                                    โ•‘
โ•‘                                                              โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

๐Ÿ”‘ Key Observations:

  1. Union merges sets: ๐Ÿค Cities 0 and 1 merged into one set

  2. Path compression not needed here: โšก Trees are already flat (depth 1)

  3. Union by rank keeps balanced: ๐Ÿ“Š Prevented unnecessary depth

  4. Counting leaders gives answer: ๐ŸŽฏ 2 leaders = 2 provinces

  5. Efficient processing: โœจ Each connection processed in O(ฮฑ(n)) โ‰ˆ O(1)


๐Ÿ“Š Complexity Analysis

DFS/BFS Approaches

Time Complexity: O(nยฒ) โšก

Where n = number of cities

For each city: O(n)
  DFS/BFS visits: O(n) in worst case
  Check all connections: O(n)

Total: O(n) cities ร— O(n) = O(nยฒ)

Example: n = 200
  Operations: 40,000 โœ… Very fast!

Space Complexity: O(n) ๐Ÿ“ฆ

Recursion stack (DFS): O(n) worst case
Queue (BFS): O(n) worst case
Visited array: O(n)

Total: O(n) ๐Ÿ“ฆ

Union-Find Approach

Time Complexity: O(nยฒ ร— ฮฑ(n)) โšก

Where ฮฑ(n) is inverse Ackermann function

Initialize: O(n)
Process connections: O(nยฒ) pairs
  Each union/find: O(ฮฑ(n)) โ‰ˆ O(1) amortized!

Total: O(nยฒ) practically!

ฮฑ(n) is INCREDIBLY small:
  n = 10^80 (atoms in universe): ฮฑ(n) โ‰ค 4!
  For all practical n: ฮฑ(n) โ‰ค 5

So effectively O(nยฒ)! โšก

Space Complexity: O(n) ๐Ÿ“ฆ

Parent array: O(n)
Rank array: O(n)

Total: O(n) ๐Ÿ“ฆ


๐Ÿ”— Union-Find Pattern Deep Dive

When to Use Union-Find ๐ŸŽฏ

โœ… Perfect for Union-Find:
  โ€ข "Count connected components"
  โ€ข "Are X and Y connected?"
  โ€ข "Merge two groups"
  โ€ข "Dynamic connectivity"
  โ€ข "Disjoint sets"

๐Ÿ” Recognition keywords:
  โ€ข "Connected"
  โ€ข "Group"
  โ€ข "Component"
  โ€ข "Cluster"
  โ€ข "Merge"
  โ€ข "Union"

๐Ÿ“Š Problem types:
  โ€ข Network connectivity
  โ€ข Friend circles
  โ€ข Island counting (alternative)
  โ€ข Redundant connections
  โ€ข Graph connectivity

Union-Find Template ๐Ÿ“‹

class UnionFind {
    private int[] parent;
    private int[] rank;

    // Constructor: Initialize
    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;  // Each is own parent
        }
    }

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

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

        if (rootX == rootY) {
            return false;  // Already connected
        }

        // Union by rank
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }

        return true;  // Successfully united
    }

    // Check if connected
    public boolean connected(int x, int y) {
        return find(x) == find(y);
    }

    // Count components
    public int countComponents() {
        int count = 0;
        for (int i = 0; i < parent.length; i++) {
            if (parent[i] == i) count++;
        }
        return count;
    }
}

Optimizations Explained ๐Ÿ”

Path Compression: โšก

Without path compression:
  Chain: 0 โ†’ 1 โ†’ 2 โ†’ 3 โ†’ 4
  find(4): 4 hops to reach 0!

With path compression:
  After find(4):
  All point directly to 0!

     0
   / | \ \
  1  2  3  4

  Next find(4): 1 hop! โšก

Implementation:
  parent[x] = find(parent[x])

This makes trees flat!
Future operations: O(1)! ๐Ÿš€

Union by Rank: ๐Ÿ“Š

Without union by rank:
  Might create long chains:
  0 โ†’ 1 โ†’ 2 โ†’ 3 ... โ†’ n
  Bad! O(n) finds!

With union by rank:
  Always attach smaller tree to larger:
  Keeps trees balanced!

Example:
  Tree A (rank 2):      Tree B (rank 1):
      0                     5
     / \                    |
    1   2                   6
    |
    3

  Union A and B:
  Attach B under A (A has higher rank):
      0
     / | \
    1  2  5
    |     |
    3     6

  Still balanced! Depth increases slowly!

Guarantees: O(log n) depth worst case
With path compression: O(ฮฑ(n)) amortized! ๐ŸŒŸ


๐Ÿ“ Quick Revision Notes

๐ŸŽฏ Core Concept

Number of Provinces finds connected components in undirected graph ๐Ÿ“Š. Three approaches: DFS (start from each unvisited, explore component) ๐ŸŒŠ, BFS (queue-based exploration) ๐Ÿ”„, Union-Find (merge connected cities, count disjoint sets) ๐Ÿ”—. Union-Find uses parent array to track set leaders ๐Ÿ‘ฅ. Path compression makes trees flat โšก. Union by rank keeps balanced ๐Ÿ“Š. Count nodes where parent[i] == i = answer! ๐ŸŽฏ Time: O(nยฒ) โšก, Space: O(n) ๐Ÿ“ฆ!

โšก Quick Implementation (Union-Find)

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;

public class Solution {
  public int findCircleNum(int[][] isConnected) {
    // // Approach 1 - DFS
    // return dfs(isConnected);

    // Approach 2 - BFS
    return bfs(isConnected);
  }

  private int bfs(int[][] cities) {
    int count = 0;
    HashSet<Integer> visited = new HashSet<>();
    Queue<Integer> queue = new LinkedList<>();

    for (int city1 = 0; city1 < cities.length; city1++) {
      if (!visited.contains(city1)) {
        visited.add(city1);
        queue.offer(city1);

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

          for (int city2 = 0; city2 < cities.length; city2++) {
            if (!visited.contains(city2) && cities[curr][city2] == 1) {
              visited.add(city2);
              queue.offer(city2);
            }
          }
        }

        count++;
      }
    }

    return count;
  }

  private int dfs(int[][] cities) {
    int count = 0;
    HashSet<Integer> visited = new HashSet<>();

    for (int city1 = 0; city1 < cities.length; city1++) {
      if (!visited.contains(city1)) {
        visited.add(city1);
        dfsUtil(city1, cities, visited);

        count++;
      }
    }

    return count;
  }

  private void dfsUtil(int city1, int[][] cities, HashSet<Integer> visited) {
    for (int city2 = 0; city2 < cities.length; city2++) {
      if (!visited.contains(city2) && cities[city1][city2] == 1) {
        visited.add(city2);
        dfsUtil(city2, cities, visited);
      }
    }
  }

  public static void main(String[] args) {
    Solution s = new Solution();
    System.out.println(s.findCircleNum(new int[][] { { 1, 1, 0 }, { 1, 1, 0 }, { 0, 0, 1 } })); // 2
    System.out.println(s.findCircleNum(new int[][] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } })); // 3
  }
}

๐Ÿ”‘ Key Insights

Union-Find Three Operations: ๐Ÿ”—

The foundation of Union-Find:

1๏ธโƒฃ MAKE-SET: parent[i] = i
   Create individual sets

2๏ธโƒฃ FIND: Follow parent pointers to root
   find(x):
     if (parent[x] != x)
       parent[x] = find(parent[x])  // Path compression!
     return parent[x]

3๏ธโƒฃ UNION: Merge two sets
   union(x, y):
     rootX = find(x)
     rootY = find(y)
     parent[rootX] = rootY  // Simple version

These three operations solve ALL connectivity problems! ๐ŸŽฏ

Path Compression Magic: โšก

Why it's brilliant:

Without compression:
  Long chain: 0 โ†’ 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5
  find(5): 5 hops! โŒ

With compression:
  First find(5): 5 hops
  But flattens tree:
       0
      /|\\\
     1 2 3 4 5

  Next find(5): 1 hop! โœ…
  Next find(4): 1 hop! โœ…
  Next find(3): 1 hop! โœ…

All future finds: O(1)! โšก

Implementation: Just one line!
  parent[x] = find(parent[x])

This recursively flattens entire path!
Genius! ๐ŸŒŸ

Union by Rank Strategy: ๐Ÿ“Š

Why rank matters:

Bad union (always attach right to left):
  Union(0,1): 0 โ†’ 1
  Union(1,2): 0 โ†’ 1 โ†’ 2
  Union(2,3): 0 โ†’ 1 โ†’ 2 โ†’ 3

  Result: Long chain! โŒ

Good union (by rank):
  Track tree "height" (rank)
  Always attach shorter tree to taller!

  Union trees of rank 1 and 2:
    Attach rank-1 under rank-2
    Final rank: still 2! โœ…

  Union trees of equal rank:
    Attach either way
    Final rank: increase by 1

  Keeps trees balanced! ๐Ÿ“Š
  Guarantees: depth โ‰ค log n

With path compression + rank:
  Amortized: O(ฮฑ(n)) โ‰ˆ O(1)! โšก

Nearly constant time! Magic! โœจ

Counting Components: ๐ŸŽฏ

The beautiful trick:

After all unions, count leaders!

Leader = node where parent[i] == i

Example:
  parent = [0, 0, 2, 0, 4]

  Check each:
    parent[0] == 0? YES โœ… Leader 1
    parent[1] == 1? NO  โŒ
    parent[2] == 2? YES โœ… Leader 2
    parent[3] == 3? NO  โŒ
    parent[4] == 4? YES โœ… Leader 3

  Count = 3 components! ๐ŸŽฏ

Why this works:
  Each leader represents one disjoint set
  Non-leaders point to their leader

Simple and elegant! ๐ŸŒŸ

๐ŸŽช Memory Aid

"Union-Find keeps sets apart" ๐Ÿ”—
"Path compression makes it smart" โšก
"Union by rank keeps trees flat" ๐Ÿ“Š
"Count leaders, and that's that!" ๐ŸŽฏ โœจ

โš ๏ธ Don't Forget

  • Initialize parent[i] = i - everyone starts separate! ๐Ÿ—๏ธ
  • Path compression in find - flatten while finding! โšก
  • Union by rank - attach smaller to larger! ๐Ÿ“Š
  • Check rootX != rootY - before union! ๐Ÿ”
  • Count where parent[i] == i - these are leaders! ๐Ÿ‘‘
  • Three approaches all work - choose based on context! ๐ŸŽฏ

๐ŸŽ‰ Congratulations!

You've learned Union-Find - a fundamental algorithm pattern! ๐ŸŒŸ

What You Learned:

โœ… Union-Find data structure - parent array tracking! ๐Ÿ”—
โœ… Path compression - O(1) amortized finds! โšก
โœ… Union by rank - balanced trees! ๐Ÿ“Š
โœ… Connected components - in any graph! ๐ŸŽฏ
โœ… Dynamic connectivity - real-time queries! ๐Ÿš€

The Beautiful Pattern:

Union-Find solves connectivity in 3 operations:
  1. MAKE-SET: Initialize
  2. FIND: Get leader
  3. UNION: Merge sets

With optimizations:
  - Path compression: O(ฮฑ(n)) finds
  - Union by rank: balanced trees

Result: Nearly O(1) operations! โšก

This will be your foundation for:
  โœ… Redundant connections
  โœ… Accounts merge
  โœ… Number of islands (alternative)
  โœ… Graph connectivity
  โœ… Minimum spanning tree

Master this pattern = unlock graph algorithms! ๐Ÿ”‘

Next problems will build on this foundation! ๐Ÿš€
Union-Find is your new superpower! ๐Ÿ’ชโœจ๐ŸŽฏ