[Medium] 721. Accounts Merge
Given a list of accounts where each element accounts[i] is a list of strings, where the first element accounts[i][0] is a name, and the rest of the elements are emails representing emails of the account.
Now, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some common email to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name.
After merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails in sorted order. The accounts themselves can be returned in any order.
Examples
Example 1:
Input: accounts = [["John","johnsmith@mail.com","john_newyork@mail.com"],["John","johnsmith@mail.com","john00@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]]
Output: [["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]]
Explanation:
The first and second John's accounts are merged because they share the email "johnsmith@mail.com".
The third John's account is not merged because it doesn't share any email with the other accounts.
Example 2:
Input: accounts = [["Gabe","Gabe0@m.co","Gabe3@m.co","Gabe1@m.co"],["Kevin","Kevin3@m.co","Kevin5@m.co","Kevin0@m.co"],["Ethan","Ethan5@m.co","Ethan4@m.co","Ethan0@m.co"],["Hanzo","Hanzo3@m.co","Hanzo1@m.co","Hanzo0@m.co"],["Fern","Fern5@m.co","Fern1@m.co","Fern0@m.co"]]
Output: [["Ethan","Ethan0@m.co","Ethan4@m.co","Ethan5@m.co"],["Gabe","Gabe0@m.co","Gabe1@m.co","Gabe3@m.co"],["Hanzo","Hanzo0@m.co","Hanzo1@m.co","Hanzo3@m.co"],["Kevin","Kevin0@m.co","Kevin3@m.co","Kevin5@m.co"],["Fern","Fern0@m.co","Fern1@m.co","Fern5@m.co"]]
Constraints
1 <= accounts.length <= 10002 <= accounts[i].length <= 101 <= accounts[i][0].length <= 10accounts[i][j]forj > 0is a valid email.
Thinking Process
- Email as Unique Identifier: Names can be duplicated, but emails are unique
- DFS explores one branch fully before backtracking.
- Mark visited nodes to avoid cycles on graphs.
- Return aggregated results from children to the parent.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Recursive DFS (this problem) | O(n) | O(h) stack | Natural for trees and graphs |
| Iterative DFS (stack) | O(n) | O(n) | Avoid recursion depth limits |
| DFS with memoization | O(n) | O(n) | Overlapping subproblems on graphs |
| Backtracking DFS | O(2^n) typical | O(n) | Enumerate choices with pruning |
Solution
Solution: Union-Find (Disjoint Set Union)
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def unionSet(self, x, y):
px = self.find(x)
py = self.find(y)
self.parent[py] = px
class Solution:
def accountsMerge(self, accounts):
emailToIdx = {}
emailToName = {}
# 1: Assign ID to each email
idx = 0
for account in accounts:
name = account[0]
for i in range(1, len(account)):
email = account[i]
if email not in emailToIdx:
emailToIdx[email] = idx
idx += 1
emailToName[email] = name
uf = UnionFind(idx)
# 2: Union emails in same account
for account in accounts:
firstEmail = account[1]
firstIdx = emailToIdx[firstEmail]
for i in range(2, len(account)):
nextEmail = account[i]
uf.unionSet(firstIdx, emailToIdx[nextEmail])
# 3: Group by root
from collections import defaultdict
idxToEmails = defaultdict(list)
for email in emailToIdx:
root = uf.find(emailToIdx[email])
idxToEmails[root].append(email)
# 4: Build result
merged = []
for emails in idxToEmails.values():
emails.sort()
name = emailToName[emails[0]]
merged.append([name] + emails)
return merged
Solution Explanation
Approach: Recursive DFS (this problem)
Key idea: 1. Email as Unique Identifier: Names can be duplicated, but emails are unique
How the code works:
- Email as Unique Identifier: Names can be duplicated, but emails are unique
- DFS explores one branch fully before backtracking.
- Mark visited nodes to avoid cycles on graphs.
- Return aggregated results from children to the parent.
Walkthrough — input accounts = [["John","johnsmith@mail.com","john_newyork@mail.com"],["John","johnsmith@mail.com","john00@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]], expected output [["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]]:
The first and second John’s accounts are merged because they share the email “johnsmith@mail.com”. The third John’s account is not merged because it doesn’t share any email with the other accounts.
Algorithm Explanation:
- Union-Find Class (Lines 1-23):
- Constructor: Initialize
parentarray where each element is its own parent unionSet: Union two sets by makingidx2’s root point toidx1’s rootfind: Find root with path compression (flattens the tree for efficiency)
- Constructor: Initialize
- Step 1: Assign IDs (Lines 30-41):
- Iterate through all accounts
- For each email in an account:
- If email not seen before, assign a unique
idand map to name - Store mappings:
emailToIdx[email] = idandemailToName[email] = name
- If email not seen before, assign a unique
- Step 2: Union Emails (Lines 43-53):
- For each account, union all emails in that account
- Strategy: Union all emails to the first email in the account
- This groups all emails from the same account together
- Step 3: Group by Root (Lines 55-61):
- For each email, find its root parent using
uf.find() - Group emails by their root parent in
idxToEmails - Emails with the same root belong to the same merged account
- For each email, find its root parent using
- Step 4: Build Result (Lines 63-73):
- For each group of emails:
- Sort emails alphabetically
- Get the name from the first email (all emails in group have same name)
- Create account:
[name, email1, email2, ...] - Add to result
- For each group of emails:
Why This Works:
- Union-Find Groups: Emails in the same account are unioned, so they share the same root
- Transitive Merging: If account A shares email with B, and B shares with C, then A, B, C are all merged (transitive property)
- Path Compression: Makes
findoperations efficient (nearly O(1) amortized) - Email as Key: Using emails (not names) correctly handles duplicate names
Example Walkthrough:
For accounts = [["John","a@mail.com","b@mail.com"],["John","a@mail.com","c@mail.com"],["Mary","d@mail.com"]]:
Step 1: Assign IDs
emailToIdx: {
"a@mail.com": 0,
"b@mail.com": 1,
"c@mail.com": 2,
"d@mail.com": 3
}
emailToName: {
"a@mail.com": "John",
"b@mail.com": "John",
"c@mail.com": "John",
"d@mail.com": "Mary"
}
Step 2: Union emails
Account 1: ["John","a@mail.com","b@mail.com"]
- Union(0, 1): parent[1] = 0
- parent = [0, 0, 2, 3]
Account 2: ["John","a@mail.com","c@mail.com"]
- Union(0, 2): parent[2] = 0
- parent = [0, 0, 0, 3]
Account 3: ["Mary","d@mail.com"]
- No union needed (only one email)
- parent = [0, 0, 0, 3]
Step 3: Group by root
idxToEmails: {
0: ["a@mail.com", "b@mail.com", "c@mail.com"],
3: ["d@mail.com"]
}
Step 4: Build result
Group 0:
- Sort: ["a@mail.com", "b@mail.com", "c@mail.com"]
- Name: "John"
- Account: ["John", "a@mail.com", "b@mail.com", "c@mail.com"]
Group 3:
- Sort: ["d@mail.com"]
- Name: "Mary"
- Account: ["Mary", "d@mail.com"]
Result: [
["John", "a@mail.com", "b@mail.com", "c@mail.com"],
["Mary", "d@mail.com"]
]
Complexity Analysis:
- Time Complexity: O(n × k × α(n × k) + n × k × log(n × k))
n= number of accounts,k= average emails per account- O(n × k) for assigning IDs
- O(n × k × α(n × k)) for union operations (α is inverse Ackermann, nearly constant)
- O(n × k × log(n × k)) for sorting emails in each group
- Total: O(n × k × log(n × k)) dominated by sorting
- Space Complexity: O(n × k)
emailToIdx: O(n × k) for all emailsemailToName: O(n × k) for all emailsidxToEmails: O(n × k) for grouped emailsparentarray: O(n × k)Common Mistakes
- Single email per account:
[["John","a@mail.com"]]→ return[["John","a@mail.com"]] - No shared emails: All accounts remain separate
- All emails shared: All accounts merge into one
-
Duplicate emails in same account: Handled correctly (only assigned one ID)
- Using names as keys: Names can be duplicated, causing incorrect merging
- Not sorting emails: Result must have sorted emails
- Wrong union logic: Must union all emails in an account, not just pairs
- Missing path compression: Without it,
findcan be O(n) instead of O(α(n)) - Name mismatch: Assuming all emails in merged account have same name (they do, but need to verify)
Related Problems
- LC 323: Number of Connected Components - Count connected components
- LC 547: Number of Provinces - Similar connected components problem
- LC 684: Redundant Connection - Union-Find for cycle detection
- LC 990: Satisfiability of Equality Equations - Union-Find for equality constraints
Key Takeaways
- Email as Unique Identifier: Names can be duplicated, but emails are unique
- Union-Find for Grouping: Efficiently groups emails that belong together
- Transitive Merging: If A shares email with B, and B shares with C, then A, B, C are merged
- Path Compression: Critical for efficiency in Union-Find operations
References
- LC 721: Accounts Merge on LeetCode
- LeetCode Discuss — LC 721: Accounts Merge
- LeetCode Editorial (may require premium)