Accounts Merge

Medium DFS

Problem

Given a list of accounts where each account contains a name and a list of emails, merge accounts that share at least one email and return the merged accounts with unique emails sorted lexicographically.

  • 1 ≤ accounts.length ≤ 1000
  • 2 ≤ accounts[i].length ≤ 10
  • 1 ≤ accounts[i][j].length ≤ 30
  • accounts[i][0] consists of English letters.
  • accounts[i][j] (for j > 0) is a valid email.

Example

Input: accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"], ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]]
Output: [["John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com"], ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]]

The accounts with emails "johnsmith@mail.com" appear in two accounts, so these accounts are merged. The merged account contains all unique emails sorted lexicographically. The other accounts remain separate because they share no emails.

Approach

Straightforward Solution

A brute-force approach would compare every pair of accounts for shared emails, merging them repeatedly until no overlaps remain. This approach is inefficient (potentially O(n^2 * m^2)) and impractical for large inputs.

Core Observation

Accounts sharing emails form connected components in a graph where emails are nodes and edges connect emails appearing in the same account. Merging accounts is equivalent to finding connected components of this graph.

Path to Optimal

Recognizing the problem as graph connectivity allows building an undirected graph where each email is a node connected to other emails in the same account. Using DFS to find connected components groups all emails belonging to the same user. This approach efficiently merges accounts by traversing each connected component once.

Optimal Approach

Construct a graph mapping emails to their neighbors and a map from email to account name. Perform DFS on unvisited emails to collect connected components. For each component, sort the emails and prepend the associated name. Return the list of merged accounts. This approach runs in O(N * M log M) where N is number of accounts and M is max emails per account.

Time

O(N * M log M)

Building the graph takes O(N * M) where N is number of accounts and M is max emails per account. DFS visits each email once. Sorting each connected component's emails dominates with O(M log M) per component, leading to overall O(N * M log M).

Space

O(N * M)

The graph and email-to-name map store all emails and their connections, requiring space proportional to total emails.

Pattern Spotlight

DFS (Graph Connected Components)

When merging entities connected by shared attributes, model the problem as a graph and use DFS to find connected components, which represent merged groups.

Solution

1class Solution:
2 def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
3 graph = defaultdict(list)
4 email_to_name = {}
5
6 for account in accounts:
7 name = account[0]
8 first_email = account[1]
9
10 for email in account[1:]:
11 email_to_name[email] = name
12 graph[first_email].append(email)
13 graph[email].append(first_email)
14
15 visited = set()
16 res = []
17
18 def dfs(email, component_emails):
19 visited.add(email)
20 component_emails.append(email)
21
22 for neighbor in graph[email]:
23 if neighbor not in visited:
24 dfs(neighbor, component_emails)
25
26 for email in graph:
27 if email not in visited:
28 component_emails = []
29 dfs(email, component_emails)
30
31 res.append([email_to_name[email]] + sorted(component_emails))
32
33 return res

Step-by-Step Solution

1

Build Email Graph and Map Emails to Names

3graph = defaultdict(list)
4email_to_name = {}
6for account in accounts:
7 name = account[0]
8 first_email = account[1]
10 for email in account[1:]:
11 email_to_name[email] = name
12 graph[first_email].append(email)
13 graph[email].append(first_email)

Objective

To construct an undirected graph connecting emails that appear in the same account and map each email to its account name.

Key Insight

By connecting all emails in an account to the first email, the graph encodes all relationships between emails belonging to the same user. The email-to-name map allows retrieving the user's name after grouping emails. This graph representation transforms the merging problem into a connected components problem.

Interview Quick-Check

Core Logic

Connecting each email to the first email in the account creates edges that link all emails of the same user, enabling DFS to find connected components.

State & Boundaries

The email_to_name map stores the name for each email, ensuring the correct name is associated with merged emails.

Common Pitfalls & Bugs

Forgetting to add edges in both directions (undirected graph) can cause incomplete traversal during DFS.

2

Traverse Graph with DFS to Collect Connected Emails

15visited = set()
16res = []
18def dfs(email, component_emails):
19 visited.add(email)
20 component_emails.append(email)
22 for neighbor in graph[email]:
23 if neighbor not in visited:
24 dfs(neighbor, component_emails)

Objective

To perform DFS on each unvisited email to find all connected emails forming one merged account.

Key Insight

DFS explores all emails reachable from a starting email, collecting them into a component. This ensures all emails belonging to the same user are grouped together. Using a visited set prevents revisiting emails and infinite loops.

Interview Quick-Check

Core Logic

DFS recursively visits all neighbors of an email, collecting connected emails into a single component representing one merged account.

State & Boundaries

The visited set tracks emails already processed to avoid redundant work and cycles.

Common Pitfalls & Bugs

Not marking emails as visited before recursion can cause infinite recursion or duplicate processing.

3

Aggregate and Sort Emails for Each Merged Account

26for email in graph:
27 if email not in visited:
28 component_emails = []
29 dfs(email, component_emails)
31 res.append([email_to_name[email]] + sorted(component_emails))

Objective

To collect the connected emails from DFS, sort them lexicographically, prepend the account name, and add to the result list.

Key Insight

Sorting emails ensures the output meets problem requirements. Prepending the name from the email_to_name map associates the merged emails with the correct user. Appending to the result list accumulates all merged accounts for final output.

Interview Quick-Check

Core Logic

Sorting the collected emails guarantees the output format is correct and consistent.

State & Boundaries

Using the email_to_name map with any email in the component retrieves the correct account name.

Common Pitfalls & Bugs

Appending the name before sorting or failing to sort emails can cause incorrect output order.

4

Return the List of Merged Accounts

33return res

Objective

To return the final list of merged accounts after processing all connected components.

Key Insight

Returning the accumulated result completes the algorithm, providing the merged accounts as required by the problem.

Interview Quick-Check

Core Logic

Returning the result list after all DFS traversals ensures all accounts are merged and included.

Line Analysis

This solution has 5 Critical lines interviewers watch for.

Line 12 Critical
graph[first_email].append(email)

Add an edge from the first email to the current email in the graph.

Connecting the first email to all others in the account creates edges that link emails belonging to the same user.

Line 13 Critical
graph[email].append(first_email)

Add an edge from the current email back to the first email to maintain an undirected graph.

Adding edges in both directions ensures the graph is undirected, which is necessary for correct DFS traversal of connected components.

Line 19 Critical
visited.add(email)

Mark the current email as visited.

Marking emails as visited before recursion prevents cycles and duplicate visits, ensuring correctness and efficiency.

Line 24 Critical
dfs(neighbor, component_emails)

Recursively call DFS on the unvisited neighbor email.

Recursion explores the full connected component by visiting all reachable emails.

Line 29 Critical
dfs(email, component_emails)

Perform DFS starting from the current unvisited email to collect connected emails.

This call discovers all emails connected to the starting email, representing one merged account.

Line 3 Core
graph = defaultdict(list)

Initialize a graph as a default dictionary of lists to store email connections.

This data structure efficiently represents the undirected graph where each email maps to its connected emails, enabling quick adjacency lookups.

Line 15 Core
visited = set()

Initialize a set to track visited emails during DFS.

The visited set prevents revisiting emails, avoiding infinite loops and redundant processing during DFS.

Line 18 Core
def dfs(email, component_emails):

Define a recursive DFS function to explore connected emails.

DFS is the core traversal method to find all emails connected to a given email, representing one merged account.

Line 23 Core
if neighbor not in visited:

Check if the neighbor email has not been visited.

Visiting only unvisited neighbors prevents infinite recursion and redundant work.

Line 27 Core
if email not in visited:

Check if the current email has not been visited.

Starting DFS only from unvisited emails avoids redundant traversals of already processed components.

Line 31 Core
res.append([email_to_name[email]] + sorted(component_emails))

Append the merged account to the result list with the name and sorted emails.

Sorting emails and prepending the name formats the merged account correctly for output, fulfilling problem requirements.

Line 4 Core
email_to_name = {}

Initialize a dictionary to map each email to its account name.

Mapping emails to names allows retrieval of the correct user name after grouping emails, which is essential for constructing the final merged accounts.

Line 8 Core
first_email = account[1]

Identify the first email in the account to serve as a connection hub.

Using the first email as a hub simplifies graph construction by connecting all other emails in the account to it, ensuring connectivity.

Line 11 Core
email_to_name[email] = name

Map the current email to the account name.

This ensures that every email can be traced back to the correct user name for final output.

Line 20 Core
component_emails.append(email)

Add the current email to the component's email list.

Collecting emails during DFS builds the full set of emails belonging to one merged account.

Line 22 Core
for neighbor in graph[email]:

Iterate over all neighbors of the current email in the graph.

Exploring neighbors is essential to traverse the entire connected component of emails.

Line 26 Core
for email in graph:

Iterate over all emails in the graph to find unvisited starting points for DFS.

This loop ensures all connected components are found by starting DFS from each unvisited email.

Line 10 Standard
for email in account[1:]:

Iterate over all emails in the current account starting from the first email.

This loop processes each email to build graph edges and map emails to names.

Line 28 Standard
component_emails = []

Initialize a list to collect emails in the current connected component.

This list temporarily stores emails found by DFS before sorting and adding to results.

Line 6 Standard
for account in accounts:

Iterate over each account in the input list.

Processing each account is necessary to build the graph and email-to-name mapping for all emails.

Line 7 Standard
name = account[0]

Extract the account name from the first element of the account list.

The name is associated with all emails in the account and is needed for the email-to-name map.

Line 16 Standard
res = []

Initialize a list to store the merged account results.

This list accumulates all merged accounts to be returned at the end.

Line 33 Standard
return res

Return the list of merged accounts.

Returning the accumulated results completes the algorithm and provides the final answer.

Test Your Understanding

Why is DFS on the email graph the correct method to merge accounts?

Because emails connected by edges represent belonging to the same user, DFS finds all emails reachable from a starting email, effectively grouping all emails of one user into a connected component.

Related Problems

DFS pattern

Don't just read it. Drill it.

Reconstruct Accounts Merge from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Accounts Merge drill

or drill a free problem