Bird
Raised Fist0
Interview Prepunion-findmediumAmazonFacebookGoogle

Number of Provinces

Choose your preparation mode4 modes available
🎯
Number of Provinces
mediumGRAPHAmazonFacebookGoogle

Imagine a country with several cities connected by roads. We want to find out how many distinct groups of cities are connected directly or indirectly, representing provinces.

💡 This problem asks us to find how many groups of interconnected nodes exist in a graph represented by an adjacency matrix. Beginners often struggle because the graph is given in matrix form, and they must realize that the problem reduces to counting connected components, which can be solved efficiently using Union-Find or DFS.
📋
Problem Statement

You are given an n x n matrix isConnected where isConnected[i][j] = 1 if the ith city and the jth city are directly connected, and 0 otherwise. A province is a group of directly or indirectly connected cities and no other cities outside of the group. Return the total number of provinces.

1 ≤ n ≤ 10^5isConnected[i][j] is either 0 or 1isConnected[i][i] = 1 for all iisConnected[i][j] = isConnected[j][i]
💡
Example
Input"[[1,1,0],[1,1,0],[0,0,1]]"
Output2

Cities 0 and 1 are connected, forming one province. City 2 is isolated, forming another province.

  • n = 1 → output 1 (single city is one province)
  • All cities connected → output 1 (one big province)
  • No cities connected except self → output n (each city is its own province)
  • Large sparse matrix with many isolated nodes → output equals number of isolated groups
⚠️
Common Mistakes
Not marking visited nodes leading to infinite recursion

Stack overflow or infinite loop during DFS

✅ Always mark nodes as visited before recursive DFS calls

Union-Find without path compression causing slow find operations

Solution times out on large inputs due to deep trees

✅ Implement path compression in find() method

Iterating over entire matrix instead of upper triangle in Union-Find

Redundant unions and slower performance

✅ Only iterate j from i+1 to n-1 to avoid duplicate checks

Counting provinces incorrectly by counting visited nodes instead of connected components

Wrong answer due to misunderstanding what to count

✅ Increment province count only when starting DFS or union sets

Not handling self-connections properly (diagonal entries)

Incorrect unions or missed provinces

✅ Ignore self-connections or ensure they don't cause redundant unions

🧠
Brute Force (DFS on Adjacency Matrix)
💡 This approach uses a straightforward graph traversal to find connected components. It is intuitive and helps understand the problem deeply before optimizing.

Intuition

We treat the matrix as a graph and perform DFS from each unvisited city to mark all connected cities as visited, counting each DFS call as one province.

Algorithm

  1. Initialize a visited array to track visited cities.
  2. Iterate over each city; if not visited, perform DFS to mark all connected cities.
  3. Increment province count for each DFS call.
  4. Return the total count of provinces.
💡 The challenge is to correctly traverse all connected nodes without revisiting, which DFS naturally handles by recursion and visited tracking.
</>
Code
from typing import List

def findCircleNum(isConnected: List[List[int]]) -> int:
    n = len(isConnected)
    visited = [False] * n

    def dfs(city: int):
        visited[city] = True
        for adj_city in range(n):
            if isConnected[city][adj_city] == 1 and not visited[adj_city]:
                dfs(adj_city)

    provinces = 0
    for city in range(n):
        if not visited[city]:
            dfs(city)
            provinces += 1
    return provinces

# Driver code
if __name__ == '__main__':
    matrix = [[1,1,0],[1,1,0],[0,0,1]]
    print(findCircleNum(matrix))  # Output: 2
Line Notes
visited = [False] * nInitialize visited list to avoid revisiting cities
def dfs(city: int):Define DFS to explore all connected cities from current city
if isConnected[city][adj_city] == 1 and not visited[adj_city]Check if adjacent city is connected and unvisited before DFS
provinces += 1Increment province count after exploring a new connected component
import java.util.*;

public class NumberOfProvinces {
    public static int findCircleNum(int[][] isConnected) {
        int n = isConnected.length;
        boolean[] visited = new boolean[n];

        int provinces = 0;
        for (int city = 0; city < n; city++) {
            if (!visited[city]) {
                dfs(city, isConnected, visited);
                provinces++;
            }
        }
        return provinces;
    }

    private static void dfs(int city, int[][] isConnected, boolean[] visited) {
        visited[city] = true;
        for (int adjCity = 0; adjCity < isConnected.length; adjCity++) {
            if (isConnected[city][adjCity] == 1 && !visited[adjCity]) {
                dfs(adjCity, isConnected, visited);
            }
        }
    }

    public static void main(String[] args) {
        int[][] matrix = {{1,1,0},{1,1,0},{0,0,1}};
        System.out.println(findCircleNum(matrix)); // Output: 2
    }
}
Line Notes
boolean[] visited = new boolean[n];Track visited cities to prevent repeated DFS
if (!visited[city])Start DFS only from unvisited cities to find new provinces
visited[city] = true;Mark city as visited to avoid cycles
provinces++;Count each DFS call as one province
#include <iostream>
#include <vector>
using namespace std;

void dfs(int city, const vector<vector<int>>& isConnected, vector<bool>& visited) {
    visited[city] = true;
    for (int adjCity = 0; adjCity < (int)isConnected.size(); adjCity++) {
        if (isConnected[city][adjCity] == 1 && !visited[adjCity]) {
            dfs(adjCity, isConnected, visited);
        }
    }
}

int findCircleNum(const vector<vector<int>>& isConnected) {
    int n = isConnected.size();
    vector<bool> visited(n, false);
    int provinces = 0;
    for (int city = 0; city < n; city++) {
        if (!visited[city]) {
            dfs(city, isConnected, visited);
            provinces++;
        }
    }
    return provinces;
}

int main() {
    vector<vector<int>> matrix = {{1,1,0},{1,1,0},{0,0,1}};
    cout << findCircleNum(matrix) << endl; // Output: 2
    return 0;
}
Line Notes
vector<bool> visited(n, false);Initialize visited vector to track explored cities
if (!visited[city])Only start DFS from unvisited cities to find new provinces
visited[city] = true;Mark current city visited to avoid revisiting
provinces++;Increment province count after DFS completes
function findCircleNum(isConnected) {
    const n = isConnected.length;
    const visited = new Array(n).fill(false);

    function dfs(city) {
        visited[city] = true;
        for (let adjCity = 0; adjCity < n; adjCity++) {
            if (isConnected[city][adjCity] === 1 && !visited[adjCity]) {
                dfs(adjCity);
            }
        }
    }

    let provinces = 0;
    for (let city = 0; city < n; city++) {
        if (!visited[city]) {
            dfs(city);
            provinces++;
        }
    }
    return provinces;
}

// Test
const matrix = [[1,1,0],[1,1,0],[0,0,1]];
console.log(findCircleNum(matrix)); // Output: 2
Line Notes
const visited = new Array(n).fill(false);Create visited array to mark explored cities
if (isConnected[city][adjCity] === 1 && !visited[adjCity])Check connectivity and visitation before DFS
visited[city] = true;Mark city visited to prevent infinite loops
provinces++;Count each DFS initiation as a new province
⏱
Complexity
TimeO(n^2)
SpaceO(n)

We visit each city once and check all its connections, resulting in O(n^2) due to adjacency matrix traversal. Space is O(n) for visited array and recursion stack.

💡 For n=1000, this means about 1,000,000 checks, which is feasible but not optimal for very large n.
Interview Verdict: Accepted but not optimal for very large inputs

This approach works well for moderate input sizes and helps understand the problem, but can be slow for very large graphs due to O(n^2) complexity.

🧠
Union-Find (Disjoint Set Union) Approach
💡 Union-Find is a classic data structure to efficiently track connected components. This approach reduces repeated traversals by merging connected cities into sets.

Intuition

We treat each city as a node in a disjoint set. For every connection, we union the sets of the two cities. The number of unique parents after all unions is the number of provinces.

Algorithm

  1. Initialize parent array where each city is its own parent.
  2. Iterate over the upper triangle of the matrix to avoid duplicate checks.
  3. For each connected pair, union their sets.
  4. Count how many cities are their own parent to find number of provinces.
💡 Union-Find avoids repeated DFS by merging sets, making connectivity checks faster and simpler.
</>
Code
from typing import List

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n
        self.count = n

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        rootX = self.find(x)
        rootY = self.find(y)
        if rootX != rootY:
            if self.rank[rootX] < self.rank[rootY]:
                self.parent[rootX] = rootY
            elif self.rank[rootX] > self.rank[rootY]:
                self.parent[rootY] = rootX
            else:
                self.parent[rootY] = rootX
                self.rank[rootX] += 1
            self.count -= 1


def findCircleNum(isConnected: List[List[int]]) -> int:
    n = len(isConnected)
    uf = UnionFind(n)
    for i in range(n):
        for j in range(i + 1, n):
            if isConnected[i][j] == 1:
                uf.union(i, j)
    return uf.count

# Driver code
if __name__ == '__main__':
    matrix = [[1,1,0],[1,1,0],[0,0,1]]
    print(findCircleNum(matrix))  # Output: 2
Line Notes
self.parent = list(range(n))Initialize each city as its own parent
if self.parent[x] != x:Path compression to flatten tree for faster finds
if rootX != rootY:Only union if roots differ to avoid cycles
self.count -= 1Decrease province count when two sets merge
public class NumberOfProvinces {
    static class UnionFind {
        int[] parent, rank;
        int count;

        UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            count = n;
            for (int i = 0; i < n; i++) parent[i] = i;
        }

        int find(int x) {
            if (parent[x] != x) parent[x] = find(parent[x]);
            return parent[x];
        }

        void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            if (rootX != rootY) {
                if (rank[rootX] < rank[rootY]) parent[rootX] = rootY;
                else if (rank[rootX] > rank[rootY]) parent[rootY] = rootX;
                else {
                    parent[rootY] = rootX;
                    rank[rootX]++;
                }
                count--;
            }
        }
    }

    public static int findCircleNum(int[][] isConnected) {
        int n = isConnected.length;
        UnionFind uf = new UnionFind(n);
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (isConnected[i][j] == 1) uf.union(i, j);
            }
        }
        return uf.count;
    }

    public static void main(String[] args) {
        int[][] matrix = {{1,1,0},{1,1,0},{0,0,1}};
        System.out.println(findCircleNum(matrix)); // Output: 2
    }
}
Line Notes
parent = new int[n];Each city starts as its own parent
if (parent[x] != x) parent[x] = find(parent[x]);Path compression for efficient find
if (rootX != rootY)Union only if roots differ to merge sets
count--;Decrement count when two sets merge
#include <iostream>
#include <vector>
using namespace std;

class UnionFind {
public:
    vector<int> parent, rank;
    int count;

    UnionFind(int n) : parent(n), rank(n, 0), count(n) {
        for (int i = 0; i < n; i++) parent[i] = i;
    }

    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }

    void unionSet(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] < rank[rootY]) parent[rootX] = rootY;
            else if (rank[rootX] > rank[rootY]) parent[rootY] = rootX;
            else {
                parent[rootY] = rootX;
                rank[rootX]++;
            }
            count--;
        }
    }
};

int findCircleNum(const vector<vector<int>>& isConnected) {
    int n = isConnected.size();
    UnionFind uf(n);
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (isConnected[i][j] == 1) uf.unionSet(i, j);
        }
    }
    return uf.count;
}

int main() {
    vector<vector<int>> matrix = {{1,1,0},{1,1,0},{0,0,1}};
    cout << findCircleNum(matrix) << endl; // Output: 2
    return 0;
}
Line Notes
parent(n), rank(n, 0), count(n)Initialize parent and rank arrays and count provinces
if (parent[x] != x) parent[x] = find(parent[x]);Path compression to speed up find
if (rootX != rootY)Union only if roots differ to merge sets
count--;Reduce province count when union merges two sets
class UnionFind {
    constructor(n) {
        this.parent = Array.from({length: n}, (_, i) => i);
        this.rank = new Array(n).fill(0);
        this.count = n;
    }

    find(x) {
        if (this.parent[x] !== x) {
            this.parent[x] = this.find(this.parent[x]);
        }
        return this.parent[x];
    }

    union(x, y) {
        const rootX = this.find(x);
        const rootY = this.find(y);
        if (rootX !== rootY) {
            if (this.rank[rootX] < this.rank[rootY]) {
                this.parent[rootX] = rootY;
            } else if (this.rank[rootX] > this.rank[rootY]) {
                this.parent[rootY] = rootX;
            } else {
                this.parent[rootY] = rootX;
                this.rank[rootX]++;
            }
            this.count--;
        }
    }
}

function findCircleNum(isConnected) {
    const n = isConnected.length;
    const uf = new UnionFind(n);
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            if (isConnected[i][j] === 1) uf.union(i, j);
        }
    }
    return uf.count;
}

// Test
const matrix = [[1,1,0],[1,1,0],[0,0,1]];
console.log(findCircleNum(matrix)); // Output: 2
Line Notes
this.parent = Array.from({length: n}, (_, i) => i);Initialize each city as its own parent
if (this.parent[x] !== x)Path compression to flatten tree for faster find
if (rootX !== rootY)Union only if roots differ to merge sets
this.count--;Decrement count when two sets merge
⏱
Complexity
TimeO(n^2 * α(n))
SpaceO(n)

We iterate over the upper triangle of the matrix (O(n^2)) and perform union/find operations which are almost O(1) amortized (α(n) is inverse Ackermann function). Space is O(n) for parent and rank arrays.

💡 For n=1000, this is about 1,000,000 operations but union-find operations are very fast, making this approach efficient.
Interview Verdict: Accepted and efficient for large inputs

Union-Find is the preferred approach in interviews for this problem due to its efficiency and clarity in managing connected components.

🧠
DFS with Adjacency List Conversion
💡 Converting the adjacency matrix to adjacency lists can reduce overhead in sparse graphs and make DFS traversal more efficient.

Intuition

Instead of checking all cities for each city, we first build adjacency lists to only traverse actual connections, then perform DFS to count provinces.

Algorithm

  1. Convert the adjacency matrix to adjacency lists.
  2. Initialize visited array.
  3. Perform DFS on each unvisited city using adjacency lists.
  4. Count the number of DFS calls as provinces.
💡 This approach reduces redundant checks by focusing only on actual connections, improving efficiency on sparse graphs.
</>
Code
from typing import List

def findCircleNum(isConnected: List[List[int]]) -> int:
    n = len(isConnected)
    graph = [[] for _ in range(n)]
    for i in range(n):
        for j in range(n):
            if isConnected[i][j] == 1 and i != j:
                graph[i].append(j)

    visited = [False] * n

    def dfs(city: int):
        visited[city] = True
        for neighbor in graph[city]:
            if not visited[neighbor]:
                dfs(neighbor)

    provinces = 0
    for city in range(n):
        if not visited[city]:
            dfs(city)
            provinces += 1
    return provinces

# Driver code
if __name__ == '__main__':
    matrix = [[1,1,0],[1,1,0],[0,0,1]]
    print(findCircleNum(matrix))  # Output: 2
Line Notes
graph = [[] for _ in range(n)]Create adjacency list for each city
if isConnected[i][j] == 1 and i != jAdd edge only if cities are connected and distinct
for neighbor in graph[city]:Traverse only actual neighbors to reduce checks
provinces += 1Count each DFS call as one province
import java.util.*;

public class NumberOfProvinces {
    public static int findCircleNum(int[][] isConnected) {
        int n = isConnected.length;
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < n; i++) graph.add(new ArrayList<>());

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (isConnected[i][j] == 1 && i != j) {
                    graph.get(i).add(j);
                }
            }
        }

        boolean[] visited = new boolean[n];

        int provinces = 0;
        for (int city = 0; city < n; city++) {
            if (!visited[city]) {
                dfs(city, graph, visited);
                provinces++;
            }
        }
        return provinces;
    }

    private static void dfs(int city, List<List<Integer>> graph, boolean[] visited) {
        visited[city] = true;
        for (int neighbor : graph.get(city)) {
            if (!visited[neighbor]) dfs(neighbor, graph, visited);
        }
    }

    public static void main(String[] args) {
        int[][] matrix = {{1,1,0},{1,1,0},{0,0,1}};
        System.out.println(findCircleNum(matrix)); // Output: 2
    }
}
Line Notes
List<List<Integer>> graph = new ArrayList<>();Initialize adjacency list for graph
if (isConnected[i][j] == 1 && i != j)Add edge only for connected distinct cities
for (int neighbor : graph.get(city))DFS visits only neighbors to reduce unnecessary checks
provinces++Count each DFS call as a province
#include <iostream>
#include <vector>
using namespace std;

void dfs(int city, const vector<vector<int>>& graph, vector<bool>& visited) {
    visited[city] = true;
    for (int neighbor : graph[city]) {
        if (!visited[neighbor]) dfs(neighbor, graph, visited);
    }
}

int findCircleNum(const vector<vector<int>>& isConnected) {
    int n = isConnected.size();
    vector<vector<int>> graph(n);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (isConnected[i][j] == 1 && i != j) {
                graph[i].push_back(j);
            }
        }
    }

    vector<bool> visited(n, false);
    int provinces = 0;
    for (int city = 0; city < n; city++) {
        if (!visited[city]) {
            dfs(city, graph, visited);
            provinces++;
        }
    }
    return provinces;
}

int main() {
    vector<vector<int>> matrix = {{1,1,0},{1,1,0},{0,0,1}};
    cout << findCircleNum(matrix) << endl; // Output: 2
    return 0;
}
Line Notes
vector<vector<int>> graph(n);Create adjacency list for each city
if (isConnected[i][j] == 1 && i != j)Add edge only for connected distinct cities
for (int neighbor : graph[city])DFS visits only neighbors to reduce overhead
provinces++Count each DFS call as a province
function findCircleNum(isConnected) {
    const n = isConnected.length;
    const graph = Array.from({length: n}, () => []);

    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            if (isConnected[i][j] === 1 && i !== j) {
                graph[i].push(j);
            }
        }
    }

    const visited = new Array(n).fill(false);

    function dfs(city) {
        visited[city] = true;
        for (const neighbor of graph[city]) {
            if (!visited[neighbor]) dfs(neighbor);
        }
    }

    let provinces = 0;
    for (let city = 0; city < n; city++) {
        if (!visited[city]) {
            dfs(city);
            provinces++;
        }
    }
    return provinces;
}

// Test
const matrix = [[1,1,0],[1,1,0],[0,0,1]];
console.log(findCircleNum(matrix)); // Output: 2
Line Notes
const graph = Array.from({length: n}, () => []);Initialize adjacency list for graph
if (isConnected[i][j] === 1 && i !== j)Add edge only for connected distinct cities
for (const neighbor of graph[city])DFS visits only neighbors to reduce unnecessary checks
provinces++Count each DFS call as a province
⏱
Complexity
TimeO(n^2)
SpaceO(n + e)

Building adjacency lists takes O(n^2). DFS visits each node and edge once, so total time remains O(n^2). Space is O(n) for visited plus O(e) for adjacency lists.

💡 This approach is more efficient for sparse graphs but still O(n^2) in worst case due to matrix conversion.
Interview Verdict: Accepted and useful for sparse graphs

This approach is a good alternative when the graph is sparse, reducing unnecessary checks during DFS traversal.

📊
All Approaches - One-Glance Tradeoffs
💡 Union-Find is the best approach to implement in interviews due to efficiency and clarity. DFS approaches are good for understanding and smaller inputs.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute Force DFSO(n^2)O(n)Yes (deep recursion possible)N/AGood to mention for clarity, rarely coded fully
2. Union-FindO(n^2 * α(n))O(n)NoN/APreferred approach to code in interviews
3. DFS with Adjacency ListO(n^2)O(n + e)YesN/AGood alternative for sparse graphs
💼
Interview Strategy
💡 Use this guide to understand the problem deeply, practice coding all approaches, and prepare to explain tradeoffs clearly in interviews.

How to Present

Step 1: Clarify the problem and constraints.Step 2: Describe the brute force DFS approach to find connected components.Step 3: Explain the Union-Find data structure and how it optimizes the solution.Step 4: Optionally mention adjacency list optimization for sparse graphs.Step 5: Code the chosen approach and test with examples.

Time Allocation

Clarify: 2min → Approach: 3min → Code: 10min → Test: 5min. Total ~20min

What the Interviewer Tests

Interviewer checks your understanding of graph connectivity, ability to implement DFS or Union-Find correctly, and optimization skills.

Common Follow-ups

  • What if the input was given as an edge list? → Use Union-Find or adjacency list directly.
  • How to optimize space if n is very large? → Use Union-Find with path compression and rank.
💡 Follow-ups test your flexibility with input formats and your knowledge of Union-Find optimizations.
🔍
Pattern Recognition

When to Use

1) Input is a graph or connectivity matrix, 2) Need to find connected components, 3) Graph is undirected, 4) Counting groups or clusters

Signature Phrases

directly or indirectly connectednumber of provincesconnected components

NOT This Pattern When

Problems involving shortest paths, cycles detection, or directed graphs are different patterns.

Similar Problems

Connected Components in Graph - same concept of counting groupsFriend Circles - identical problem with different naming

Practice

(1/5)
1. Given the following code snippet implementing the union-find approach for Swim in Rising Water, and the input grid:
grid = [
  [0, 2],
  [1, 3]
]

# After sorting cells by elevation, what is the earliest time returned by swimInWater(grid)?
What is the returned value?
easy
A. 1
B. 2
C. 0
D. 3

Solution

  1. Step 1: Sort cells by elevation

    Sorted cells: (0,0,0), (1,1,0), (2,0,1), (3,1,1)
  2. Step 2: Union adjacent cells with elevation ≤ current

    At elevation=0, only (0,0) is active; no union with end. At elevation=1, union (1,0) with (0,0) possible. At elevation=2, union (0,1) with (0,0) or (1,0). At elevation=3, union (1,1) with neighbors, connecting start and end.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Earliest time when start and end connected is elevation 3 [OK]
Hint: Trace union-find merges in elevation order [OK]
Common Mistakes:
  • Returning elevation before union-find connects start and end
  • Confusing cell indices or union conditions
  • Off-by-one errors in elevation comparisons
2. Examine the following buggy union-find code used in Kruskal's MST. Which line contains the subtle bug that can cause incorrect MST results on disconnected graphs?
medium
A. Line: if self.size[rx] < self.size[ry]:
B. Line: edges.sort(key=lambda x: x[2])
C. Line: return mst_weight # No connectivity check
D. Line: self.parent[x] = self.parent[self.parent[x]] # Path compression

Solution

  1. Step 1: Identify missing connectivity validation

    The code returns mst_weight without verifying if all nodes are connected, which can produce incorrect MST for disconnected graphs.
  2. Step 2: Understand impact of missing check

    Without checking if edges_used == n-1 or connectivity, the MST may be incomplete, returning partial sums.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Connectivity check missing causes incomplete MST [OK]
Hint: Always verify MST connects all nodes before returning [OK]
Common Mistakes:
  • Forgetting to check if MST spans all vertices
  • Assuming union-find union count guarantees connectivity
  • Ignoring disconnected graph edge cases
3. What is the time complexity of the optimal union-find approach with early contradiction detection for satisfiability of equality equations, assuming n equations and a fixed alphabet of 26 variables?
medium
A. O(n^2) due to repeated find operations without optimization
B. O(n α(n)) where α(n) is the inverse Ackermann function, nearly constant
C. O(n log n) because of union by rank and path compression overhead
D. O(n) since each equation is processed once with constant-time unions

Solution

  1. Step 1: Identify union-find operations

    Each union or find operation runs in amortized O(α(n)) time due to path compression and union by rank.
  2. Step 2: Count total operations

    Processing n equations involves at most 2n union/find calls, so total time is O(n α(n)).
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Union-find nearly linear time complexity [OK]
Hint: Inverse Ackermann function is nearly constant [OK]
Common Mistakes:
  • Confusing α(n) with log n
  • Assuming O(n) without considering find complexity
4. Suppose the problem is modified so that edges can be added multiple times (i.e., duplicate edges allowed), and you need to find the first edge that creates a cycle considering duplicates. Which modification to the union-find approach correctly handles this scenario?
hard
A. Before union, check if the edge already exists in a hash set; if yes, return it immediately as redundant
B. Use union-find as usual but also maintain a visited edges set to detect duplicates causing cycles
C. Modify union-find to count the number of times an edge is added and only detect cycle when count > 1
D. Ignore duplicates and run union-find as usual; duplicates won't affect cycle detection

Solution

  1. Step 1: Understand duplicate edges impact

    Duplicate edges connect the same nodes multiple times, creating immediate cycles on second insertion.
  2. Step 2: Modify algorithm to detect duplicates

    Using a hash set to track edges allows detecting duplicates before union, returning the duplicate edge as redundant immediately.
  3. Step 3: Why other options fail

    Ignoring duplicates misses immediate cycle; counting edge additions in union-find is not standard; maintaining visited edges separately duplicates hash set logic.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Hash set lookup detects duplicates in O(1) before union [OK]
Hint: Detect duplicates with a hash set before union [OK]
Common Mistakes:
  • Assuming union-find alone detects duplicates
  • Trying to count edges in union-find
  • Ignoring duplicate edges impact
5. Suppose the problem is extended so that variables can be reused multiple times in equations, and inequalities can be chained (e.g., a==b, b!=c, c==a). Which modification to the union-find approach correctly handles early contradiction detection in this scenario?
hard
A. Process all equalities and inequalities in a single pass, unioning and checking contradictions immediately
B. First union all equalities, then check all inequalities for contradictions, returning false if any found
C. Use union-find with early contradiction detection by checking inequalities during union operations and aborting immediately on conflict
D. Replace union-find with a graph coloring approach to handle chained inequalities

Solution

  1. Step 1: Understand reuse and chaining impact

    Variables appearing multiple times and chained inequalities require detecting contradictions as soon as they arise to avoid unnecessary work.
  2. Step 2: Early contradiction detection

    Checking inequalities during union operations allows immediate detection of conflicts, improving efficiency and correctness.
  3. Step 3: Compare alternatives

    Single pass union and check can miss contradictions; two-pass delays detection; graph coloring is more complex and less efficient here.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Early contradiction detection aborts on conflict [OK]
Hint: Check contradictions during union to detect conflicts early [OK]
Common Mistakes:
  • Checking inequalities only after all unions
  • Trying single pass union and check without order
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard