28 · Union-Find & Minimum Spanning Trees
#721 Accounts Merge
the problem
In plain words
Each account is [name, email1, email2, …]. Two accounts belong to the same person if they share any email (names alone prove nothing). Merge them and return each person as [name, sorted emails…], in any order.
in [["John","johnsmith@mail.com","john_newyork@mail.com"],["John","johnsmith@mail.com","john00@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]]
out [["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]]
The first two Johns share johnsmith@mail.com; the third John shares nothing.
in [["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"]]
out [["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"]]
Nobody shares an email, so nothing merges — each account just gets its emails sorted.
- 1 ≤ accounts.length ≤ 1000
- 2 ≤ accounts[i].length ≤ 10
- 1 ≤ accounts[i][j].length ≤ 30
- Names are English letters; emails are valid
your turn
Try it before you peek
Read the examples, say the brute force out loud, then write code: here or on LeetCode ↗. Stuck for 20 minutes? Open one hint at a time.
Solve it in the browser
LeetCode-style: complete the class. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.
Solutions are hidden so you can try first. Even 20 minutes of real effort makes the answer stick far better than reading it.
Marking the problem solved, or opening every hint, also shows it.
the aha
Key insight
patternUnion-Find (DSU)Graph DFS / BFS traversal
where this runsIdentity resolution — deciding that two sign-ups with a shared phone number or email are one customer — is this exact merge, run over far more records.
Emails are the nodes. Every account says "all these emails belong together" — union them with the account's first email. Each final set is one person.
solutions
From brute force to optimal
Connect each email to the first email of its account (a star), then each DFS component is one person.
- Build email → neighbours and email → name maps.
- DFS (iterative, with ArrayDeque) from each unseen email to collect its component.
- Sort the component and prepend the name.
N = total emails. Building and traversing is O(N); sorting the groups dominates.
Graph, maps and visited set.
class Solution { public List<List<String>> accountsMerge(List<List<String>> accounts) { Map<String, List<String>> graph = new HashMap<>(); // email → neighbouring emails Map<String, String> owner = new HashMap<>(); // email → name for (List<String> acc : accounts) { String first = acc.get(1); for (int i = 1; i < acc.size(); i++) { String email = acc.get(i); owner.put(email, acc.get(0)); graph.computeIfAbsent(email, k -> new ArrayList<>()); if (i > 1) { // star: link every email to the first one of its account graph.get(first).add(email); graph.get(email).add(first); } } } Set<String> seen = new HashSet<>(); List<List<String>> result = new ArrayList<>(); for (String start : graph.keySet()) { if (!seen.add(start)) continue; List<String> emails = new ArrayList<>(); ArrayDeque<String> stack = new ArrayDeque<>(); stack.push(start); while (!stack.isEmpty()) { // iterative DFS: one component = one person String e = stack.pop(); emails.add(e); for (String nb : graph.get(e)) if (seen.add(nb)) stack.push(nb); } Collections.sort(emails); emails.add(0, owner.get(start)); result.add(emails); } return result; }}Map-based DSU: union every email with its account's first email, then group by root.
- parent.putIfAbsent(email, email) and remember email → name.
- For each account, union all its emails with the first one.
- Group emails by
find(email)intoTreeSets (sorted), then build the output rows.
This map DSU uses path compression only, so finds are O(log N) amortized; sorting each group (TreeSet inserts) also costs a log. Add union by size and the DSU part drops to O(N·α(N)) — the sort still dominates.
parent map, owner map and the groups.
class Solution { private final Map<String, String> parent = new HashMap<>(); private String find(String x) { String p = parent.get(x); if (!p.equals(x)) { p = find(p); parent.put(x, p); // path compression } return p; } public List<List<String>> accountsMerge(List<List<String>> accounts) { Map<String, String> owner = new HashMap<>(); for (List<String> acc : accounts) { for (int i = 1; i < acc.size(); i++) { parent.putIfAbsent(acc.get(i), acc.get(i)); // every email starts as its own set owner.put(acc.get(i), acc.get(0)); } } for (List<String> acc : accounts) { String root = find(acc.get(1)); for (int i = 2; i < acc.size(); i++) { String other = find(acc.get(i)); if (!other.equals(root)) parent.put(other, root); // union with the first email } } Map<String, TreeSet<String>> groups = new HashMap<>(); // root → sorted emails for (String email : parent.keySet()) { groups.computeIfAbsent(find(email), k -> new TreeSet<>()).add(email); } List<List<String>> result = new ArrayList<>(); for (Map.Entry<String, TreeSet<String>> g : groups.entrySet()) { List<String> row = new ArrayList<>(); row.add(owner.get(g.getKey())); row.addAll(g.getValue()); result.add(row); } return result; }}Dry run
in the room
How to say it in the interview
talk track
- Clarify: same name does not mean same person; only shared emails do.
- Say why you union with the first email: it makes each account one connected star without adding k² edges.
- Both DFS and DSU are O(N log N) because of the final sort — pick the one you can write cleanly.
traps
- Merging by name.
- Forgetting to sort the emails in each output row.
- Duplicate emails inside one account — sets handle them; lists would repeat them.
what they ask next
Follow-ups
Accounts keep arriving one at a time?
Union-find is built for this: each new account is a few unions, near O(1) each. Build the sorted groups only when someone asks for them.
People can also be linked by a shared phone number?
Make phones nodes too, in the same DSU. Prefix the keys ("e:" for email, "p:" for phone) so the two kinds never clash.
before you submit
Edge cases to test
- Same name, no shared email → different people, separate rows.
- Two accounts linked only through a third account must still merge.
- The same email listed twice inside one account must appear once.
- A single account with a single email → one row.
practise next
Similar problems
- #547Number of ProvincesMediumin Union-Find & MST
- #990Satisfiability of Equality EquationsMediumin Union-Find & MST
- #128Longest Consecutive SequenceMediumin Hashing