Facebook Pixel

721. Accounts Merge

Problem Description

You are given a list of accounts where each account is represented as a list of strings. The first string in each account list is the person's name, and the remaining strings are email addresses belonging to that account.

Your task is to merge accounts that belong to the same person. Two accounts belong to the same person if they share at least one common email address. Once two accounts are identified as belonging to the same person, all their emails should be combined.

Important points to consider:

  • Even if two accounts have the same name, they might belong to different people (names are not unique)
  • A person can have multiple accounts initially, but all accounts belonging to the same person will have the same name
  • If account A shares an email with account B, and account B shares a different email with account C, then all three accounts belong to the same person (transitive property)

The output should be a list of merged accounts where:

  • The first element is the person's name
  • The remaining elements are all unique email addresses in sorted order
  • The accounts themselves can be returned in any order

For example, if you have:

  • ["John", "john@mail.com", "john123@mail.com"]
  • ["John", "john123@mail.com", "john@work.com"]
  • ["Mary", "mary@mail.com"]

The first two accounts share "john123@mail.com", so they belong to the same person and should be merged into:

  • ["John", "john123@mail.com", "john@mail.com", "john@work.com"] (emails in sorted order)
  • ["Mary", "mary@mail.com"]

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The problem involves accounts (nodes) that are connected through shared email addresses (edges). When two accounts share an email, they form a connection in an implicit graph structure.

Is it a tree?

  • No: The connections between accounts can form cycles (account A connects to B, B connects to C, and C could connect back to A through shared emails), and accounts can have multiple connections, so it's not a tree structure.

Is the problem related to directed acyclic graphs?

  • No: The relationships between accounts are undirected (if account A shares an email with account B, then B shares it with A) and can contain cycles.

Is the problem related to shortest paths?

  • No: We're not finding shortest paths between accounts; we're identifying which accounts belong together.

Does the problem involve connectivity?

  • Yes: The core of the problem is finding connected components - groups of accounts that are all connected through shared email addresses. Each connected component represents all accounts belonging to the same person.

Disjoint Set Union

  • Yes: This is the suggested approach. DSU (Union-Find) is perfect for this problem as it efficiently handles merging sets (accounts) and finding which set an account belongs to.

Conclusion: While the flowchart leads us to DSU (which is what the solution uses), DFS is also a valid approach for this problem. After building the graph of email connections, we can use DFS to traverse each connected component, collecting all emails that belong to the same person. Both DSU and DFS effectively solve the connectivity problem of finding and merging accounts that share email addresses.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that this is fundamentally a graph connectivity problem. Each account can be thought of as a node, and when two accounts share an email address, they're connected by an edge. The challenge is that these connections are transitive - if account A shares an email with account B, and account B shares a different email with account C, then all three accounts belong to the same person.

Think of it like finding groups of friends on a social network. If person A knows person B, and person B knows person C, then they're all part of the same social circle, even if A and C don't directly know each other.

The Union-Find (Disjoint Set Union) data structure is perfect for this scenario because:

  1. Efficient Merging: When we discover two accounts share an email, we can quickly merge them into the same group using union() operation.

  2. Group Identification: We can easily find which group any account belongs to using find() operation.

  3. Transitive Connections: Union-Find naturally handles transitive relationships - if we union A with B, then union B with C, the data structure automatically knows that A, B, and C are all in the same group.

The approach works by:

  • First, iterating through all accounts and their emails
  • Using a hash table to track which account index each email belongs to
  • When we encounter an email that's already been seen, we know the current account and the previous account with that email belong to the same person, so we union them
  • After processing all accounts, we use the Union-Find structure to group all emails by their root representative
  • Finally, we construct the merged accounts using the grouped emails

This is more efficient than building an explicit graph and running DFS on each component, as Union-Find operations are nearly constant time with path compression and union by rank optimizations.

Learn more about Depth-First Search, Breadth-First Search, Union Find and Sorting patterns.

Solution Approach

The solution implements a Union-Find data structure with optimizations (path compression and union by size) to efficiently merge accounts. Let's walk through the implementation step by step:

1. Union-Find Data Structure Setup

First, we create a UnionFind class with the following components:

  • self.p: Parent array where p[i] initially points to itself (each account is its own group)
  • self.size: Tracks the size of each group for union by size optimization

The find() method uses path compression - when finding the root of a node, it directly connects all nodes in the path to the root, making future operations faster.

The union() method merges two groups by connecting the smaller group's root to the larger group's root (union by size), which keeps the tree balanced.

2. Building Email-to-Account Mapping

d = {}  # email -> account index mapping
for i, (_, *emails) in enumerate(accounts):
    for email in emails:
        if email in d:
            uf.union(i, d[email])  # merge current account with previous account
        else:
            d[email] = i  # map email to current account index

We iterate through each account and its emails:

  • If an email hasn't been seen before, we map it to the current account's index in dictionary d
  • If an email has been seen, it means the current account and the previously seen account share this email, so we union them

3. Grouping Emails by Root Account

g = defaultdict(set)
for i, (_, *emails) in enumerate(accounts):
    root = uf.find(i)
    g[root].update(emails)

After all unions are complete, we iterate through accounts again:

  • Find the root representative for each account using uf.find(i)
  • Add all emails from that account to the set associated with its root
  • Using a set automatically handles duplicate emails across merged accounts

4. Constructing the Final Result

return [[accounts[root][0]] + sorted(emails) for root, emails in g.items()]

For each group (identified by its root):

  • Take the name from the root account (all merged accounts have the same name)
  • Sort the collected emails alphabetically
  • Combine the name and sorted emails into a single list

The time complexity is O(N * Ξ±(N)) for union-find operations (where Ξ± is the inverse Ackermann function, practically constant) plus O(E log E) for sorting emails, where N is the number of accounts and E is the total number of emails.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with these accounts:

  • Account 0: ["Alice", "alice@gmail.com", "alice@work.com"]
  • Account 1: ["Alice", "alice@work.com", "alice@yahoo.com"]
  • Account 2: ["Bob", "bob@gmail.com"]
  • Account 3: ["Alice", "alice@yahoo.com", "alice@hotmail.com"]

Step 1: Initialize Union-Find

  • Create UnionFind with 4 accounts
  • Parent array: [0, 1, 2, 3] (each account points to itself)
  • Size array: [1, 1, 1, 1]

Step 2: Build Email-to-Account Mapping

Processing Account 0:

  • alice@gmail.com β†’ not seen, map to account 0: d = {"alice@gmail.com": 0}
  • alice@work.com β†’ not seen, map to account 0: d = {"alice@gmail.com": 0, "alice@work.com": 0}

Processing Account 1:

  • alice@work.com β†’ already seen (mapped to 0), so union(1, 0)
    • After union: parent = [0, 0, 2, 3], size = [2, 1, 1, 1]
  • alice@yahoo.com β†’ not seen, map to account 1: d = {..., "alice@yahoo.com": 1}

Processing Account 2:

  • bob@gmail.com β†’ not seen, map to account 2: d = {..., "bob@gmail.com": 2}

Processing Account 3:

  • alice@yahoo.com β†’ already seen (mapped to 1), so union(3, 1)
    • find(1) returns 0 (since 1's parent is 0)
    • So we actually union(3, 0)
    • After union: parent = [0, 0, 2, 0], size = [3, 1, 1, 1]
  • alice@hotmail.com β†’ not seen, map to account 3

Step 3: Group Emails by Root

  • Account 0: find(0) = 0, add emails to group 0
  • Account 1: find(1) = 0, add emails to group 0
  • Account 2: find(2) = 2, add emails to group 2
  • Account 3: find(3) = 0, add emails to group 0

Groups formed:

  • Group 0: {"alice@gmail.com", "alice@work.com", "alice@yahoo.com", "alice@hotmail.com"}
  • Group 2: {"bob@gmail.com"}

Step 4: Construct Final Result

  • Group 0: ["Alice", "alice@gmail.com", "alice@hotmail.com", "alice@work.com", "alice@yahoo.com"] (sorted)
  • Group 2: ["Bob", "bob@gmail.com"]

The final output shows that accounts 0, 1, and 3 all belonged to the same Alice and were successfully merged through their shared emails.

Solution Implementation

1from typing import List
2from collections import defaultdict
3
4
5class UnionFind:
6    """Union-Find (Disjoint Set Union) data structure with path compression and union by size."""
7  
8    def __init__(self, n: int) -> None:
9        # Initialize parent array where each element is its own parent
10        self.parent = list(range(n))
11        # Track the size of each component for union by size optimization
12        self.size = [1] * n
13
14    def find(self, x: int) -> int:
15        """Find the root of element x with path compression."""
16        if self.parent[x] != x:
17            # Path compression: make every node point directly to the root
18            self.parent[x] = self.find(self.parent[x])
19        return self.parent[x]
20
21    def union(self, a: int, b: int) -> bool:
22        """
23        Union two elements a and b using union by size.
24        Returns True if union was performed, False if already in same set.
25        """
26        root_a = self.find(a)
27        root_b = self.find(b)
28      
29        # Already in the same set
30        if root_a == root_b:
31            return False
32      
33        # Union by size: attach smaller tree under root of larger tree
34        if self.size[root_a] > self.size[root_b]:
35            self.parent[root_b] = root_a
36            self.size[root_a] += self.size[root_b]
37        else:
38            self.parent[root_a] = root_b
39            self.size[root_b] += self.size[root_a]
40      
41        return True
42
43
44class Solution:
45    def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
46        """
47        Merge accounts that belong to the same person based on common emails.
48      
49        Args:
50            accounts: List of accounts where each account is [name, email1, email2, ...]
51          
52        Returns:
53            List of merged accounts with unique sorted emails
54        """
55        # Initialize Union-Find structure for account indices
56        union_find = UnionFind(len(accounts))
57      
58        # Map email to account index for finding accounts with common emails
59        email_to_account_index = {}
60      
61        # First pass: build email mapping and union accounts with common emails
62        for account_index, account_data in enumerate(accounts):
63            name = account_data[0]
64            emails = account_data[1:]
65          
66            for email in emails:
67                if email in email_to_account_index:
68                    # This email exists in another account, union them
69                    existing_account_index = email_to_account_index[email]
70                    union_find.union(account_index, existing_account_index)
71                else:
72                    # First time seeing this email, map it to current account
73                    email_to_account_index[email] = account_index
74      
75        # Group emails by their root account (after union operations)
76        root_to_emails = defaultdict(set)
77      
78        for account_index, account_data in enumerate(accounts):
79            name = account_data[0]
80            emails = account_data[1:]
81          
82            # Find the root representative for this account
83            root_index = union_find.find(account_index)
84          
85            # Add all emails from this account to the root's email set
86            root_to_emails[root_index].update(emails)
87      
88        # Build the final result with sorted emails
89        result = []
90        for root_index, email_set in root_to_emails.items():
91            # Get the account name from the root account
92            account_name = accounts[root_index][0]
93            # Sort emails and prepend the name
94            merged_account = [account_name] + sorted(email_set)
95            result.append(merged_account)
96      
97        return result
98
1/**
2 * Union-Find (Disjoint Set Union) data structure with path compression and union by size
3 */
4class UnionFind {
5    private final int[] parent;     // parent[i] = parent of node i
6    private final int[] size;       // size[i] = number of nodes in subtree rooted at i
7  
8    /**
9     * Initialize Union-Find structure with n elements (0 to n-1)
10     * @param n number of elements
11     */
12    public UnionFind(int n) {
13        parent = new int[n];
14        size = new int[n];
15        for (int i = 0; i < n; i++) {
16            parent[i] = i;  // Initially, each element is its own parent
17            size[i] = 1;    // Initially, each set has size 1
18        }
19    }
20  
21    /**
22     * Find the root/representative of the set containing element x
23     * Uses path compression to optimize future operations
24     * @param x element to find root for
25     * @return root of the set containing x
26     */
27    public int find(int x) {
28        if (parent[x] != x) {
29            // Path compression: make all nodes point directly to root
30            parent[x] = find(parent[x]);
31        }
32        return parent[x];
33    }
34  
35    /**
36     * Union two sets containing elements a and b
37     * Uses union by size to keep tree balanced
38     * @param a first element
39     * @param b second element
40     * @return true if union was performed, false if already in same set
41     */
42    public boolean union(int a, int b) {
43        int rootA = find(a);
44        int rootB = find(b);
45      
46        // Already in the same set
47        if (rootA == rootB) {
48            return false;
49        }
50      
51        // Union by size: attach smaller tree under larger tree
52        if (size[rootA] > size[rootB]) {
53            parent[rootB] = rootA;
54            size[rootA] += size[rootB];
55        } else {
56            parent[rootA] = rootB;
57            size[rootB] += size[rootA];
58        }
59        return true;
60    }
61}
62
63/**
64 * Solution for Account Merge problem
65 * Groups accounts that share common emails using Union-Find
66 */
67class Solution {
68    public List<List<String>> accountsMerge(List<List<String>> accounts) {
69        int numAccounts = accounts.size();
70        UnionFind unionFind = new UnionFind(numAccounts);
71      
72        // Map to track which account index each email belongs to
73        Map<String, Integer> emailToAccountIndex = new HashMap<>();
74      
75        // Process each account and union accounts with common emails
76        for (int accountIndex = 0; accountIndex < numAccounts; accountIndex++) {
77            List<String> currentAccount = accounts.get(accountIndex);
78          
79            // Skip the name at index 0, process emails starting from index 1
80            for (int emailIndex = 1; emailIndex < currentAccount.size(); emailIndex++) {
81                String email = currentAccount.get(emailIndex);
82              
83                if (emailToAccountIndex.containsKey(email)) {
84                    // Email already seen - union current account with previous account
85                    unionFind.union(accountIndex, emailToAccountIndex.get(email));
86                } else {
87                    // First time seeing this email - map it to current account
88                    emailToAccountIndex.put(email, accountIndex);
89                }
90            }
91        }
92      
93        // Group all emails by their root account (after unions)
94        Map<Integer, Set<String>> rootToEmails = new HashMap<>();
95        for (int accountIndex = 0; accountIndex < numAccounts; accountIndex++) {
96            int rootAccount = unionFind.find(accountIndex);
97            List<String> currentAccount = accounts.get(accountIndex);
98          
99            // Add all emails (excluding name) to the set for this root
100            rootToEmails.computeIfAbsent(rootAccount, k -> new HashSet<>())
101                .addAll(currentAccount.subList(1, currentAccount.size()));
102        }
103      
104        // Build the final result with sorted emails
105        List<List<String>> mergedAccounts = new ArrayList<>();
106        for (Map.Entry<Integer, Set<String>> entry : rootToEmails.entrySet()) {
107            int rootAccountIndex = entry.getKey();
108            Set<String> emailSet = entry.getValue();
109          
110            // Sort emails alphabetically
111            List<String> sortedEmails = new ArrayList<>(emailSet);
112            Collections.sort(sortedEmails);
113          
114            // Create merged account: [name, sorted emails...]
115            List<String> mergedAccount = new ArrayList<>();
116            String accountName = accounts.get(rootAccountIndex).get(0);
117            mergedAccount.add(accountName);
118            mergedAccount.addAll(sortedEmails);
119          
120            mergedAccounts.add(mergedAccount);
121        }
122      
123        return mergedAccounts;
124    }
125}
126
1class UnionFind {
2public:
3    // Initialize Union-Find with n elements
4    UnionFind(int n) {
5        parent = vector<int>(n);
6        rank = vector<int>(n, 1);
7        // Initialize each element as its own parent (self-loop)
8        iota(parent.begin(), parent.end(), 0);
9    }
10
11    // Unite two sets containing elements a and b
12    // Returns true if they were in different sets, false if already united
13    bool unite(int a, int b) {
14        int rootA = find(a);
15        int rootB = find(b);
16      
17        // Already in the same set
18        if (rootA == rootB) {
19            return false;
20        }
21      
22        // Union by rank: attach smaller tree under root of larger tree
23        if (rank[rootA] > rank[rootB]) {
24            parent[rootB] = rootA;
25            rank[rootA] += rank[rootB];
26        } else {
27            parent[rootA] = rootB;
28            rank[rootB] += rank[rootA];
29        }
30        return true;
31    }
32
33    // Find the root of the set containing element x with path compression
34    int find(int x) {
35        if (parent[x] != x) {
36            // Path compression: make every node point directly to root
37            parent[x] = find(parent[x]);
38        }
39        return parent[x];
40    }
41
42private:
43    vector<int> parent;  // Parent array for Union-Find
44    vector<int> rank;    // Rank/size array for union by rank optimization
45};
46
47class Solution {
48public:
49    vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
50        int numAccounts = accounts.size();
51        UnionFind unionFind(numAccounts);
52      
53        // Map each email to its first occurrence account index
54        unordered_map<string, int> emailToAccountIndex;
55      
56        // Process each account
57        for (int i = 0; i < numAccounts; ++i) {
58            // Iterate through emails (skip name at index 0)
59            for (int j = 1; j < accounts[i].size(); ++j) {
60                const string& email = accounts[i][j];
61              
62                // If email already exists, merge current account with existing account
63                if (emailToAccountIndex.find(email) != emailToAccountIndex.end()) {
64                    unionFind.unite(i, emailToAccountIndex[email]);
65                } else {
66                    // First occurrence of this email, map it to current account
67                    emailToAccountIndex[email] = i;
68                }
69            }
70        }
71      
72        // Group emails by their root account (after merging)
73        unordered_map<int, set<string>> rootToEmails;
74        for (int i = 0; i < numAccounts; ++i) {
75            int rootAccount = unionFind.find(i);
76            // Add all emails from this account to its root's email set
77            // Using set to automatically sort and deduplicate emails
78            rootToEmails[rootAccount].insert(accounts[i].begin() + 1, accounts[i].end());
79        }
80      
81        // Build result: convert grouped emails to required format
82        vector<vector<string>> mergedAccounts;
83        for (const auto& [rootAccount, emailSet] : rootToEmails) {
84            // Convert set to vector (emails are already sorted)
85            vector<string> accountData(emailSet.begin(), emailSet.end());
86            // Insert account name at the beginning
87            accountData.insert(accountData.begin(), accounts[rootAccount][0]);
88            mergedAccounts.push_back(accountData);
89        }
90      
91        return mergedAccounts;
92    }
93};
94
1// Parent array for Union-Find structure
2let parent: number[];
3// Size array for Union-Find structure (used for union by rank)
4let size: number[];
5
6/**
7 * Initialize Union-Find structure with n elements
8 * @param n - Number of elements
9 */
10function initializeUnionFind(n: number): void {
11    parent = new Array(n);
12    size = new Array(n);
13  
14    // Initially, each element is its own parent with size 1
15    for (let i = 0; i < n; i++) {
16        parent[i] = i;
17        size[i] = 1;
18    }
19}
20
21/**
22 * Find the root parent of element x with path compression
23 * @param x - Element to find root for
24 * @returns Root parent of element x
25 */
26function find(x: number): number {
27    // Path compression: make all nodes point directly to root
28    if (parent[x] !== x) {
29        parent[x] = find(parent[x]);
30    }
31    return parent[x];
32}
33
34/**
35 * Union two elements a and b using union by size
36 * @param a - First element
37 * @param b - Second element
38 * @returns true if union performed, false if already in same set
39 */
40function union(a: number, b: number): boolean {
41    const rootA = find(a);
42    const rootB = find(b);
43  
44    // Already in the same set
45    if (rootA === rootB) {
46        return false;
47    }
48  
49    // Union by size: attach smaller tree under root of larger tree
50    if (size[rootA] > size[rootB]) {
51        parent[rootB] = rootA;
52        size[rootA] += size[rootB];
53    } else {
54        parent[rootA] = rootB;
55        size[rootB] += size[rootA];
56    }
57  
58    return true;
59}
60
61/**
62 * Merge accounts with common emails
63 * @param accounts - Array of accounts where each account is [name, email1, email2, ...]
64 * @returns Merged accounts with unique emails sorted
65 */
66function accountsMerge(accounts: string[][]): string[][] {
67    const accountCount = accounts.length;
68  
69    // Initialize Union-Find for all accounts
70    initializeUnionFind(accountCount);
71  
72    // Map to store first occurrence of each email to account index
73    const emailToAccountIndex = new Map<string, number>();
74  
75    // Process each account and union accounts with common emails
76    for (let accountIndex = 0; accountIndex < accountCount; accountIndex++) {
77        // Start from index 1 to skip the name at index 0
78        for (let emailIndex = 1; emailIndex < accounts[accountIndex].length; emailIndex++) {
79            const email = accounts[accountIndex][emailIndex];
80          
81            if (emailToAccountIndex.has(email)) {
82                // Email already seen, union current account with previous account
83                const previousAccountIndex = emailToAccountIndex.get(email)!;
84                union(accountIndex, previousAccountIndex);
85            } else {
86                // First occurrence of this email
87                emailToAccountIndex.set(email, accountIndex);
88            }
89        }
90    }
91  
92    // Group emails by their root account
93    const rootToEmails = new Map<number, Set<string>>();
94  
95    for (let accountIndex = 0; accountIndex < accountCount; accountIndex++) {
96        const rootAccount = find(accountIndex);
97      
98        // Initialize email set for this root if not exists
99        if (!rootToEmails.has(rootAccount)) {
100            rootToEmails.set(rootAccount, new Set<string>());
101        }
102      
103        // Add all emails from this account to the root's email set
104        const emailSet = rootToEmails.get(rootAccount)!;
105        for (let emailIndex = 1; emailIndex < accounts[accountIndex].length; emailIndex++) {
106            emailSet.add(accounts[accountIndex][emailIndex]);
107        }
108    }
109  
110    // Build result array with merged accounts
111    const mergedAccounts: string[][] = [];
112  
113    for (const [rootAccount, emailSet] of rootToEmails.entries()) {
114        // Sort emails alphabetically
115        const sortedEmails = Array.from(emailSet).sort();
116      
117        // Create merged account: [name, ...sorted emails]
118        const accountName = accounts[rootAccount][0];
119        const mergedAccount = [accountName, ...sortedEmails];
120      
121        mergedAccounts.push(mergedAccount);
122    }
123  
124    return mergedAccounts;
125}
126

Time and Space Complexity

The time complexity is O(n Γ— m Γ— Ξ±(n) + E Γ— log E), where:

  • n is the number of accounts
  • m is the average number of emails per account
  • Ξ±(n) is the inverse Ackermann function (practically constant, nearly O(1))
  • E is the total number of unique emails across all accounts

Breaking down the operations:

  1. First loop iterates through all accounts and emails: O(n Γ— m) operations, each with a find operation that takes O(Ξ±(n)) time
  2. Second loop iterates through all accounts and emails again: O(n Γ— m) with find operations
  3. Final list comprehension sorts emails for each merged group: O(E Γ— log E) in the worst case when all emails belong to one group

Since Ξ±(n) is effectively constant and E ≀ n Γ— m, the overall time complexity simplifies to O(n Γ— m Γ— log(n Γ— m)) or approximately O(n Γ— log n) when considering m as a constant factor in typical use cases.

The space complexity is O(n + E), where:

  • Union-Find structure uses O(n) space for parent and size arrays
  • Dictionary d stores up to E unique email-to-index mappings: O(E)
  • DefaultDict g stores all unique emails: O(E)
  • Output list contains all accounts and emails: O(n Γ— m)

Since E ≀ n Γ— m, the space complexity is O(n Γ— m), which simplifies to O(n) when m is considered constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting That Names Are Not Unique Identifiers

Pitfall: A common mistake is assuming that accounts with the same name belong to the same person and should be merged. This leads to incorrectly merging accounts of different people who happen to have the same name.

Example of the mistake:

# WRONG: Grouping by name first
name_to_accounts = defaultdict(list)
for i, account in enumerate(accounts):
    name = account[0]
    name_to_accounts[name].append(i)
    # Then trying to merge within each name group

Why it's wrong: Consider these accounts:

  • ["John", "john1@mail.com"]
  • ["John", "john2@mail.com"]
  • ["John", "john1@mail.com", "john3@mail.com"]

The first and third accounts should be merged (they share john1@mail.com), but the second account belongs to a different John and should remain separate.

Solution: Only use email addresses to determine if accounts should be merged. The name is just metadata that gets carried along with the merged account.

2. Not Handling the Transitive Property of Account Relationships

Pitfall: Only checking direct email overlaps between pairs of accounts without considering transitive relationships.

Example of the mistake:

# WRONG: Only checking direct overlaps
merged = []
used = set()
for i, account1 in enumerate(accounts):
    if i in used:
        continue
    emails = set(account1[1:])
    for j, account2 in enumerate(accounts[i+1:], i+1):
        if any(email in emails for email in account2[1:]):
            emails.update(account2[1:])
            used.add(j)
    merged.append([account1[0]] + sorted(emails))

Why it's wrong: This approach might miss transitive connections. For example:

  • Account A: ["John", "a@mail.com", "b@mail.com"]
  • Account B: ["John", "b@mail.com", "c@mail.com"]
  • Account C: ["John", "c@mail.com", "d@mail.com"]

Account A connects to B through b@mail.com, and B connects to C through c@mail.com. All three should be merged, but a simple pairwise check might miss the A-C connection.

Solution: Use Union-Find (as in the provided solution) or graph traversal (DFS/BFS) to properly handle transitive relationships.

3. Modifying the Parent Array Without Path Compression

Pitfall: In the Union-Find implementation, directly modifying the parent array without proper path compression can lead to inefficient operations and potentially incorrect results.

Example of the mistake:

# WRONG: Simple find without path compression
def find(self, x):
    while self.parent[x] != x:
        x = self.parent[x]
    return x

Why it's wrong: Without path compression, the tree can become unbalanced, leading to O(n) find operations in the worst case. This degrades overall performance from nearly O(n) to O(nΒ²).

Solution: Always implement path compression in the find operation:

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

4. Not Deduplicating Emails in the Final Result

Pitfall: When collecting emails from multiple accounts into a merged account, forgetting to deduplicate them.

Example of the mistake:

# WRONG: Using list instead of set
root_to_emails = defaultdict(list)
for account_index, account_data in enumerate(accounts):
    root_index = union_find.find(account_index)
    root_to_emails[root_index].extend(account_data[1:])  # Duplicates!

Why it's wrong: If multiple original accounts share emails, those emails will appear multiple times in the merged result.

Solution: Use a set to automatically handle deduplication:

root_to_emails = defaultdict(set)
for account_index, account_data in enumerate(accounts):
    root_index = union_find.find(account_index)
    root_to_emails[root_index].update(account_data[1:])  # Set handles duplicates
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which two pointer techniques do you use to check if a string is a palindrome?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More