Accounts Merge
Medium · Graphs · Explanation and code example
Disjoint Set Union
How it works
Union-Find (Disjoint Set Union) is designed for exactly this type of problem: grouping elements into disjoint sets and merging sets efficiently. Instead of building a graph and traversing it, we assign each account an ID and union accounts that share an email. When we see an email for the first time, we record which account it belongs to. If we see it again, we union the current account with the one that first owned it. After processing all accounts, we group emails by their account's root representative.
Steps
- Initialize Union-Find with one node per account.
- Create a map from email to the first account index that contains it.
- For each account's emails:
- If the email was seen before, union the current account with the previous owner.
- Otherwise, record the current account as the owner.
- For each email, find the root of its owning account and group emails by
root. - For each group, sort the emails and prepend the account name.
- Return the merged accounts.
Complexity
- Time complexity:
O((n * m)log (n * m)) - Space complexity:
O(n * m)
Where
nis the number of accounts andmis the number of emails.
Things to watch for
Treating Same Name as Same Person
Two accounts with the same name are NOT necessarily the same person. They are only the same person if they share at least one email. Merging accounts based solely on name will produce incorrect results.
Forgetting to Sort Emails in Output
The problem requires emails in each merged account to be sorted in lexicographical order. Forgetting this step will produce results in arbitrary order that may fail validation.
Using Wrong Account Name After Merging
When merging accounts, you must use the name from one of the original accounts in the merged group. A common mistake is losing track of which account's name to use, especially in Union-Find where you need to get the name from the representative's original account.
Python code example
from typing import List
from collections import defaultdict
class UnionFind:
def __init__(self, n):
self.par = [i for i in range(n)]
self.rank = [1] * n
def find(self, x):
while x != self.par[x]:
self.par[x] = self.par[self.par[x]]
x = self.par[x]
return x
def union(self, x1, x2):
p1, p2 = self.find(x1), self.find(x2)
if p1 == p2:
return False
if self.rank[p1] > self.rank[p2]:
self.par[p2] = p1
self.rank[p1] += self.rank[p2]
else:
self.par[p1] = p2
self.rank[p2] += self.rank[p1]
return True
class Solution:
def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
uf = UnionFind(len(accounts))
emailToAcc = {} # email -> index of acc
for i, a in enumerate(accounts):
for e in a[1:]:
if e in emailToAcc:
uf.union(i, emailToAcc[e])
else:
emailToAcc[e] = i
emailGroup = defaultdict(list) # index of acc -> list of emails
for e, i in emailToAcc.items():
leader = uf.find(i)
emailGroup[leader].append(e)
res = []
for i, emails in emailGroup.items():
name = accounts[i][0]
res.append([name] + sorted(emailGroup[i]))
return res
C++ code example
class UnionFind {
vector<int> parent;
vector<int> rank;
public:
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 1);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int x) {
if (x != parent[x]) {
parent[x] = find(parent[x]);
}
return parent[x];
}
bool unionSets(int x1, int x2) {
int p1 = find(x1);
int p2 = find(x2);
if (p1 == p2) {
return false;
}
if (rank[p1] > rank[p2]) {
parent[p2] = p1;
rank[p1] += rank[p2];
} else {
parent[p1] = p2;
rank[p2] += rank[p1];
}
return true;
}
};
class Solution {
public:
vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
int n = accounts.size();
UnionFind uf(n);
unordered_map<string, int> emailToAcc; // email -> index of acc
// Build union-find structure
for (int i = 0; i < n; i++) {
for (int j = 1; j < accounts[i].size(); j++) {
const string& email = accounts[i][j];
if (emailToAcc.count(email)) {
uf.unionSets(i, emailToAcc[email]);
} else {
emailToAcc[email] = i;
}
}
}
// Group emails by leader account
map<int, vector<string>> emailGroup; // index of acc -> list of emails
for (const auto& [email, accId] : emailToAcc) {
int leader = uf.find(accId);
emailGroup[leader].push_back(email);
}
// Build result
vector<vector<string>> res;
for (auto& [accId, emails] : emailGroup) {
sort(emails.begin(), emails.end());
vector<string> merged;
merged.push_back(accounts[accId][0]);
merged.insert(merged.end(), emails.begin(), emails.end());
res.push_back(merged);
}
return res;
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.