Accounts Merge und zusammenhängende Komponenten
Gruppieren Sie Konten mit gemeinsamer E-Mail-Adresse, indem Sie E-Mails als DSU-Knoten behandeln, und sammeln Sie anschließend alle E-Mails jeder Komponente, um die zusammengeführten Konten zu rekonstruieren.
Accounts Merge und zusammenhängende Komponenten ist eine kostenlose Coding Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Coding Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Problem: Accounts Merge
Das Problem Accounts Merge (LeetCode 721) gibt eine Liste von Konten vor, wobei jedes Konto eine Liste von Zeichenketten ist: Das erste Element ist der Kontoname, die übrigen Elemente sind E-Mail-Adressen. Zwei Konten gehören derselben Person, wenn sie mindestens eine E-Mail-Adresse gemeinsam haben. Führen Sie alle Konten derselben Person zusammen und geben Sie sortierte E-Mail-Listen zurück.
Dies ist im Kern ein Problem zu zusammenhängenden Komponenten, bei dem E-Mail-Adressen die Knoten sind und ein gemeinsames Konto sie verbindet. DSU ist dafür das ideale Werkzeug: Vereinigen Sie alle E-Mail-Adressen innerhalb desselben Kontos und sammeln Sie anschließend die E-Mail-Adressen pro Komponente.
# Example input
accounts = [
['John', 'john@mail.com', 'john1@mail.com'],
['John', 'john2@mail.com'],
['Mary', 'mary@mail.com'],
['John', 'john1@mail.com', 'john2@mail.com'],
]
# john@mail.com and john1@mail.com are in account[0]
# john1@mail.com and john2@mail.com are in account[3]
# => john@, john1@, john2@ are all the same person
# Expected output:
# ['John', 'john1@mail.com', 'john2@mail.com', 'john@mail.com']
# ['Mary', 'mary@mail.com']
print('Goal: merge accounts sharing any email into one account')E-Mail-Adressen auf ganzzahlige IDs abbilden
DSU arbeitet mit ganzzahligen Indizes, aber unsere Knoten sind E-Mail-Zeichenketten. Daher müssen wir jede eindeutige E-Mail-Adresse auf eine ganzzahlige ID abbilden. Außerdem müssen wir speichern, welchem Namen jede E-Mail-Adresse gehört. Verwenden Sie ein Dictionary email_to_id, um fortlaufende IDs zu vergeben, und email_to_name, um den mit jeder E-Mail-Adresse verknüpften Kontonamen zu speichern.
Jede eindeutige E-Mail-Adresse erhält eine ID. Wenn dieselbe E-Mail-Adresse in mehreren Konten vorkommt, wird sie derselben ID zugeordnet – und durch die Vereinigung der IDs der E-Mail-Adressen innerhalb eines Kontos werden diese zu einer einzigen Komponente verbunden. Der mit der ID der Wurzel-E-Mail-Adresse verknüpfte Name ist der Name des zusammengeführten Kontos.
accounts = [
['John', 'john@mail.com', 'john1@mail.com'],
['John', 'john2@mail.com'],
['Mary', 'mary@mail.com'],
['John', 'john1@mail.com', 'john2@mail.com'],
]
email_to_id = {}
email_to_name = {}
next_id = [0]
for account in accounts:
name = account[0]
for email in account[1:]:
if email not in email_to_id:
email_to_id[email] = next_id[0]
next_id[0] += 1
email_to_name[email] = name
print('Total unique emails:', len(email_to_id))
for email, eid in email_to_id.items():
print(f' {email} => id {eid} (owner: {email_to_name[email]})')E-Mail-Adressen innerhalb jedes Kontos vereinigen
Für jedes Konto vereinigen wir die IDs aller darin aufgeführten E-Mail-Adressen. Wir wählen die erste E-Mail-Adresse des Kontos als Repräsentanten und vereinigen die ID jeder anderen E-Mail-Adresse mit ihr. Dadurch werden alle E-Mail-Adressen des Kontos zu einer Komponente verbunden.
Nach der Verarbeitung aller Konten haben E-Mail-Adressen, die gemeinsam vorkamen (direkt oder transitiv über gemeinsame E-Mail-Adressen in verschiedenen Konten), dieselbe DSU-Wurzel. Dies ist der entscheidende Schritt, der den Zusammenhang über mehrere Konten hinweg weitergibt.
class DSU:
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 union(self, x, y):
self.parent[self.find(x)] = self.find(y)
# After building email_to_id (from previous step)
# email_to_id = {'john@mail.com':0, 'john1@mail.com':1,
# 'john2@mail.com':2, 'mary@mail.com':3}
dsu = DSU(5) # 4 unique emails
# For account ['John', 'john@mail.com', 'john1@mail.com']:
dsu.union(0, 1) # john@ and john1@ share account => same component
# For account ['John', 'john1@mail.com', 'john2@mail.com']:
dsu.union(1, 2) # john1@ and john2@ share account => same component
# Now 0,1,2 all share a root; 3 (mary) is separate
print('find(0)==find(2)?', dsu.find(0) == dsu.find(2)) # True
print('find(0)==find(3)?', dsu.find(0) == dsu.find(3)) # FalseE-Mail-Adressen pro Komponente sammeln
Nachdem alle Vereinigungen abgeschlossen sind, durchlaufen wir jede E-Mail-Adresse, ermitteln ihre DSU-Wurzel und gruppieren die E-Mail-Adressen mithilfe eines Dictionarys aus Listen nach dieser Wurzel. Die ID der Wurzel wird zum Schlüssel. Anschließend ermitteln wir für jede Gruppe den Kontonamen, sortieren die E-Mail-Liste und stellen den Namen voran.
Das Sortieren der E-Mail-Adressen ist durch das Problem vorgeschrieben – innerhalb eines zusammengeführten Kontos müssen sie in lexikografischer Reihenfolge stehen. Der Name kann aus jeder E-Mail-Adresse der Gruppe ermittelt werden, da alle E-Mail-Adressen in einer Komponente derselben Person gehören.
from collections import defaultdict
# After DSU unions, group by root
def collect_components(email_to_id, email_to_name, dsu):
root_to_emails = defaultdict(list)
for email, eid in email_to_id.items():
root = dsu.find(eid)
root_to_emails[root].append(email)
result = []
for root, emails in root_to_emails.items():
# Find the name from any email in this group
name = email_to_name[emails[0]]
result.append([name] + sorted(emails))
return result
# Mock data for illustration
email_to_id = {'john@m.com':0,'john1@m.com':1,'john2@m.com':2,'mary@m.com':3}
email_to_name = {e:'John' for e in list(email_to_id)[:3]}
email_to_name['mary@m.com'] = 'Mary'
class DSU:
def __init__(self,n): self.p=list(range(n))
def find(self,x): self.p[x]=self.p[self.p[x]] if self.p[x]!=x else x; return self.p[x] if self.p[x]==x else self.find(self.p[x])
def union(self,x,y): self.p[self.find(x)]=self.find(y)
dsu=DSU(4); dsu.union(0,1); dsu.union(1,2)
for row in collect_components(email_to_id, email_to_name, dsu):
print(row)Vollständige Lösung für Accounts Merge
Hier ist die vollständige Lösung, die alle drei Schritte kombiniert: die Zuordnung von E-Mail-Adressen zu IDs erstellen, E-Mail-Adressen innerhalb jedes Kontos vereinigen und gruppierte E-Mail-Adressen nach DSU-Wurzel sammeln. Die Gesamtlaufzeit beträgt O(n × m × alpha(n × m)), wobei n die Anzahl der Konten und m die maximale Anzahl von E-Mail-Adressen pro Konto ist; effektiv ergibt sich O(n × m).
Die Speicherkomplexität beträgt O(n × m) für die E-Mail-Zuordnungen und DSU-Arrays. Diese Lösung behandelt das transitive Zusammenführen korrekt: Wenn Konto A die E-Mail-Adresse X mit Konto B teilt und Konto B die E-Mail-Adresse Y mit Konto C teilt, werden A, B und C zu einer einzigen Gruppe zusammengeführt.
from collections import defaultdict
def accounts_merge(accounts):
email_to_id = {}
email_to_name = {}
eid = 0
for account in accounts:
name = account[0]
for email in account[1:]:
if email not in email_to_id:
email_to_id[email] = eid
eid += 1
email_to_name[email] = name
parent = list(range(eid))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
parent[find(x)] = find(y)
for account in accounts:
first_id = email_to_id[account[1]]
for email in account[2:]:
union(first_id, email_to_id[email])
root_to_emails = defaultdict(list)
for email, i in email_to_id.items():
root_to_emails[find(i)].append(email)
return [[email_to_name[emails[0]]] + sorted(emails)
for emails in root_to_emails.values()]
accounts = [['John','a@m.com','b@m.com'],['John','c@m.com'],
['Mary','d@m.com'],['John','b@m.com','c@m.com']]
for row in accounts_merge(accounts):
print(row)BFS/DFS-Alternative für Accounts Merge
Ein alternativer Ansatz erstellt einen E-Mail-zu-Konten-Graphen, in dem E-Mail-Adressen Knoten sind und Kanten E-Mail-Adressen verbinden, die im selben Konto vorkommen. Anschließend findet BFS/DFS jede Zusammenhangskomponente. Der Ansatz ist zwar korrekt, erfordert aber, den Graphen explizit aufzubauen und BFS von jeder noch nicht besuchten E-Mail-Adresse auszuführen – mehr Code und schwerer nachzuvollziehen als DSU.
DSU ist übersichtlicher, weil die Union-Find-Struktur die Zugehörigkeit zu einer Komponente auf natürliche Weise darstellt, ohne dass eine explizite Adjazenzliste benötigt wird. BFS ist hier nur dann vorzuziehen, wenn Sie den tatsächlichen Pfad oder die Kette gemeinsam genutzter E-Mail-Adressen zwischen zwei Konten rekonstruieren müssen.
# BFS alternative (for comparison)
from collections import defaultdict, deque
def accounts_merge_bfs(accounts):
email_to_accounts = defaultdict(set)
for i, account in enumerate(accounts):
for email in account[1:]:
email_to_accounts[email].add(i)
visited_accounts = set()
result = []
for i, account in enumerate(accounts):
if i in visited_accounts:
continue
queue = deque([i])
emails_in_group = set()
while queue:
acc_idx = queue.popleft()
if acc_idx in visited_accounts:
continue
visited_accounts.add(acc_idx)
for email in accounts[acc_idx][1:]:
emails_in_group.add(email)
for j in email_to_accounts[email]:
queue.append(j)
result.append([account[0]] + sorted(emails_in_group))
return result
accounts = [['John','a@m.com','b@m.com'],['John','b@m.com','c@m.com'],['Mary','d@m.com']]
for row in accounts_merge_bfs(accounts):
print(row)Verallgemeinerung: Zusammenhangskomponenten in Graphen
Das Muster zum Zusammenführen von Konten lässt sich auf jedes Problem mit Zusammenhangskomponenten mit Labels verallgemeinern: Sie haben eine Menge von Elementen, einige Elemente werden als äquivalent (verbunden) festgelegt, und Sie möchten alle transitiv äquivalenten Elemente gemeinsam gruppieren. Beispiele sind Clustering-Probleme, Freundesgruppen in sozialen Netzwerken und die Erkennung doppelter Datensätze.
Der allgemeine Algorithmus lautet immer: (1) Weisen Sie jedem Element eine ganzzahlige ID zu, (2) führen Sie die IDs äquivalenter Elemente mit union zusammen, (3) gruppieren Sie die Elemente nach ihrer DSU-Wurzel. DSU ist im Wesentlichen ein Gruppierungsmechanismus für Äquivalenzrelationen.
# Generalised grouping template
def group_equivalents(items, equivalences):
item_to_id = {item: i for i, item in enumerate(items)}
n = len(items)
parent = list(range(n))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
parent[find(x)] = find(y)
for a, b in equivalences:
if a in item_to_id and b in item_to_id:
union(item_to_id[a], item_to_id[b])
groups = {}
for item in items:
root = find(item_to_id[item])
groups.setdefault(root, []).append(item)
return list(groups.values())
# Example: merging duplicate customer records
customers = ['Alice-NY','Alice-LA','Bob','Alice-TX','Carol']
links = [('Alice-NY','Alice-LA'),('Alice-LA','Alice-TX')]
print(group_equivalents(customers, links))Randfälle behandeln
Wichtige Randfälle beim Zusammenführen von Konten:
- Konten mit nur einer E-Mail-Adresse: Ein Konto mit nur einer E-Mail-Adresse bildet eine eigene Komponente, sofern kein anderes Konto diese E-Mail-Adresse gemeinsam nutzt.
- Gleicher Name, unterschiedliche Personen: Wenn „John“ in zwei Konten vorkommt, bedeutet das nicht, dass es sich um dieselbe Person handelt – nur gemeinsame E-Mail-Adressen führen Konten zusammen. Der Name wird pro E-Mail-Adresse gespeichert, nicht pro Komponente.
- Leere Konten: Ein Konto ohne E-Mail-Adressen sollte übersprungen werden, um Indexfehler zu vermeiden.
Überprüfen Sie stets, dass Ihre Lösung Konten nicht allein deshalb zusammenführt, weil sie denselben Namen haben. Die DSU-Verbindungen werden ausschließlich durch gemeinsame E-Mail-Adressen bestimmt.
# Edge case: two Johns with no shared email => separate output
accounts = [
['John', 'john_a@m.com'],
['John', 'john_b@m.com'], # different email => different component
['Mary'], # no emails => skip
]
def accounts_merge_safe(accounts):
email_to_id = {}; email_to_name = {}; eid = 0
for account in accounts:
name = account[0]
for email in account[1:]:
if email not in email_to_id:
email_to_id[email] = eid; eid += 1
email_to_name[email] = name
parent = list(range(eid))
def find(x):
while parent[x]!=x: parent[x]=parent[parent[x]]; x=parent[x]
return x
def union(x,y): parent[find(x)]=find(y)
for account in accounts:
if len(account) < 2: continue # skip no-email accounts
first = email_to_id[account[1]]
for email in account[2:]:
union(first, email_to_id[email])
from collections import defaultdict
groups = defaultdict(list)
for email, i in email_to_id.items():
groups[find(i)].append(email)
return [[email_to_name[e[0]]] + sorted(e) for e in groups.values()]
for row in accounts_merge_safe(accounts):
print(row)Anzahl der Zusammenhangskomponenten in einem Graphen
Ein verwandtes Problem (LeetCode 323) fragt nach der Anzahl der Zusammenhangskomponenten in einem ungerichteten Graphen. Es ist einfacher als accounts-merge: Initialisieren Sie DSU mit n Knoten, verarbeiten Sie alle Kanten mit union und zählen Sie anschließend die verschiedenen Wurzeln.
Die knappste Möglichkeit, Komponenten zu zählen, besteht darin, eine bei n startende Variable count zu verwalten und sie jedes Mal zu verringern, wenn eine erfolgreiche union zwei verschiedene Komponenten zusammenführt. Alternativ können Sie am Ende die Anzahl der Knoten i zählen, für die find(i) == i gilt.
def count_components(n, edges):
parent = list(range(n))
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
count = n
for u, v in edges:
pu, pv = find(u), find(v)
if pu != pv:
parent[pu] = pv
count -= 1
return count
print(count_components(5, [[0,1],[1,2],[3,4]])) # 2: {0,1,2} and {3,4}
print(count_components(5, [[0,1],[1,2],[2,3],[3,4]])) # 1: all connected
print(count_components(5, [])) # 5: no edges, all isolatedKleinste und größte Komponente
Sobald DSU die Größen der Komponenten verfolgt, können Sie Abfragen wie „Wie groß ist die größte Zusammenhangskomponente?“ oder „Wie viele Komponenten haben genau 3 Knoten?“ in O(n) beantworten, indem Sie das Größenarray an den Wurzelknoten durchlaufen.
Solche Abfragen kommen in Problemen wie „die größte verbundene Insel finden“ auf einem Gitter oder „die kleinste Netzwerkpartition bestimmen“ vor. Nachdem alle Vereinigungen abgeschlossen sind, suchen Sie nach Knoten i, für die find(i) == i gilt (diese sind Wurzeln), und untersuchen Sie ihre Größen.
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
px, py = self.find(x), self.find(y)
if px == py: return
if self.size[px] < self.size[py]: px, py = py, px
self.parent[py] = px
self.size[px] += self.size[py]
def component_stats(n, edges):
dsu = DSU(n)
for u, v in edges:
dsu.union(u, v)
sizes = [dsu.size[i] for i in range(n) if dsu.find(i) == i]
print('Component sizes:', sizes)
print('Largest component:', max(sizes))
print('Smallest component:', min(sizes))
print('Number of components:', len(sizes))
component_stats(8, [(0,1),(1,2),(3,4),(5,6),(6,7)])Interviewtipps für DSU-Aufgaben
Wenn Sie auf ein Problem mit dem Zusammenführen von Gruppen, Zusammenhangsabfragen oder dem Finden der zusätzlichen Kante stoßen, denken Sie sofort an DSU. Erwähnen Sie in Interviews beide Optimierungen (Pfadkompression + Union nach Rang/Größe), um fundierte Kenntnisse zu zeigen, selbst wenn eine einfachere naive DSU-Lösung die gegebenen Einschränkungen erfüllen würde.
Häufige Fehler, die Sie vermeiden sollten: den Fall zu vergessen, in dem beide Endpunkte bereits verbunden sind (union ist dann eine No-op), die 0-basierte und 1-basierte Indizierung falsch zu verwenden und die Ausgabe für accounts-merge nicht zu sortieren (das Problem verlangt sortierte E-Mail-Listen). Klären Sie vor dem Programmieren stets die Einschränkungen der Eingabe.
# Interview checklist for DSU problems
checklist = [
'1. Identify: is this a grouping/connectivity/cycle problem?',
'2. Map problem entities to integer node IDs if needed',
'3. Implement DSU with path compression + union by rank/size',
'4. Process all relationships (edges/pairs) with union()',
'5. Answer queries using find() and size/count tracking',
'6. Handle edge cases: already connected, single nodes, no edges',
'7. Check output format: sorted? 1-indexed? Name included?',
'8. State time complexity: O(n * alpha(n)) ~ O(n)',
]
for item in checklist:
print(item)Schnelltest
Testen Sie Ihr Verständnis der Konzepte aus Data Structures & Algorithms — Coding Interview Prep in dieser Lektion.
Lektionszusammenfassung
In dieser Lektion haben Sie gelernt: accounts-merge ist ein Problem mit Zusammenhangskomponenten, bei dem E-Mail-Adressen Knoten sind und Konten E-Mail-Adressen verbinden, DSU löst es, indem E-Mail-Adressen auf ganzzahlige IDs abgebildet, IDs innerhalb jedes Kontos vereinigt und Elemente nach ihrer Wurzel gruppiert werden und dieselbe DSU-Gruppierungsvorlage auf jedes Problem mit Äquivalenzklassen oder Clustering angewendet werden kann. Als Nächstes wechseln wir zur Bitmanipulation und beginnen mit den grundlegenden Operatoren AND, OR, XOR, NOT und den Verschiebungsoperatoren.
Häufig gestellte Fragen
Ist die Lektion „Accounts Merge und zusammenhängende Komponenten“ kostenlos?
Ja — der vollständige Text von „Accounts Merge und zusammenhängende Komponenten“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Coding Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Accounts Merge und zusammenhängende Komponenten“?
Gruppieren Sie Konten mit gemeinsamer E-Mail-Adresse, indem Sie E-Mails als DSU-Knoten behandeln, und sammeln Sie anschließend alle E-Mails jeder Komponente, um die zusammengeführten Konten zu rekons… Du übst Coding Interview Prep mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Coding Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. Coding Interview Prep auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.
Wie lange dauert die Lektion „Accounts Merge und zusammenhängende Komponenten“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Coding Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede Coding Interview Prep-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- DSU mit Pfadkompression
- Union nach Rang und die inverse-Ackermann-Schranke
- Redundante Verbindung und Zykluserkennung
- Accounts Merge und zusammenhängende Komponenten