Kontosammenslåing og sammenhengende komponenter
Gruppér kontoer som deler en e-postadresse, ved å behandle e-postadresser som DSU-noder, og samle deretter alle e-postadresser per komponent for å rekonstruere de sammenslåtte kontoene.
Kontosammenslåing og sammenhengende komponenter er en gratis leksjon i DSA Interview Prep på CoddyKit. Dette er leksjon 4 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i DSA Interview Prep, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i DSA Interview Prep inneholder totalt 4 leksjoner.
Problem: Accounts Merge
Problemet Accounts Merge (LeetCode 721) gir en liste over kontoer, der hver konto er en liste med strenger: Det første elementet er kontonavnet, og resten er e-postadresser. To kontoer tilhører samme person hvis de deler minst én e-postadresse. Slå sammen alle kontoer som tilhører samme person, og returner sorterte e-postlister.
Dette er i bunn og grunn et problem med sammenhengende komponenter, der e-postadresser er noder, og en delt konto kobler dem sammen. DSU er det ideelle verktøyet: Utfør union på alle e-postadresser i samme konto, og samle deretter e-postadressene per komponent.
# 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')Tilordning av e-postadresser til heltalls-ID-er
DSU fungerer med heltallsindekser, men nodene våre er e-poststrenger. Derfor må hver unike e-postadresse tilordnes en heltalls-ID. Det må også registreres hvilket navn som eier hver e-postadresse. Bruk en ordbok email_to_id til å tilordne fortløpende ID-er, og email_to_name til å holde oversikt over kontonavnet som er knyttet til hver e-postadresse.
Hver unike e-postadresse får én ID. Hvis den samme e-postadressen forekommer i flere kontoer, tilordnes den samme ID — og når ID-ene til e-postadressene i én konto forenes, kobles de sammen til én komponent. Navnet som er knyttet til ID-en til e-postadressen i roten, blir navnet på den sammenslåtte kontoen.
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]})')Utføre union på e-postadresser i hver konto
For hver konto utføres union på ID-ene til alle e-postadressene som står oppført sammen. Den første e-postadressen velges som representant, og ID-en til hver av de andre e-postadressene forenes med den. Slik kobles alle e-postadressene i kontoen sammen til én komponent.
Etter at alle kontoene er behandlet, har e-postadresser som forekom sammen — direkte eller transitivt gjennom delte e-postadresser på tvers av kontoer — samme DSU-rot. Dette er det avgjørende steget som sprer sammenhengen på tvers av flere kontoer.
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)) # FalseSamle e-postadresser per komponent
Etter at alle unioner er utført, går vi gjennom hver e-postadresse, finner DSU-roten og grupperer e-postadressene etter denne roten ved hjelp av en ordbok med lister. Rot-ID-en blir nøkkelen. Til slutt henter vi kontonavnet for hver gruppe, sorterer e-postlisten og setter navnet først.
Sortering av e-postadressene kreves av problemet — i en sammenslått konto må e-postadressene stå i leksikografisk rekkefølge. Navnet kan hentes fra hvilken som helst e-postadresse i gruppen, siden alle e-postadressene i én komponent tilhører samme person.
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)Fullstendig løsning på Accounts Merge
Her er den fullstendige løsningen som kombinerer alle tre stegene: bygg tilordningen fra e-postadresse til ID, utfør union på e-postadressene i hver konto, og samle grupperte e-postadresser etter DSU-rot. Den samlede tidskompleksiteten er O(n × m × alpha(n × m)), der n er antallet kontoer og m er det maksimale antallet e-postadresser per konto, noe som i praksis er O(n × m).
Plasskompleksiteten er O(n × m) for e-postkartleggingene og DSU-tabellene. Denne løsningen håndterer transitiv sammenslåing korrekt: Hvis konto A deler e-postadressen X med konto B, og konto B deler e-postadressen Y med konto C, slås A, B og C sammen til én gruppe.
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-alternativ til kontosammenslåing
En alternativ tilnærming bygger en graf fra e-postadresser til kontoer, der e-postadresser er noder og kanter kobler sammen e-postadresser som forekommer i samme konto. Deretter finner BFS/DFS hver sammenhengende komponent. Selv om dette er korrekt, krever det at grafen bygges eksplisitt, og at BFS kjøres fra hver e-postadresse som ikke er besøkt – mer kode og vanskeligere å resonnere over enn DSU.
DSU er enklere fordi union-find-strukturen naturlig representerer komponenttilhørighet uten behov for en eksplisitt naboliste. Den eneste situasjonen der BFS er å foretrekke her, er hvis du må rekonstruere den faktiske stien eller kjeden av delte e-postadresser mellom to kontoer.
# 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)Generalisering: sammenhengende komponenter i grafer
Mønsteret for kontosammenslåing kan generaliseres til alle problemer med sammenhengende komponenter med etiketter: du har en mengde elementer, noen elementer er erklært ekvivalente (forbundet), og du vil gruppere alle transitivt ekvivalente elementer sammen. Eksempler er klyngeproblemer, vennegrupper i sosiale nettverk og oppdagelse av duplikatoppføringer.
Den generelle algoritmen er alltid: (1) tilordne hver element en heltalls-ID, (2) utføre union på ID-ene til erklært ekvivalente elementer, (3) gruppere elementene etter DSU-roten. DSU er i praksis en grupperingsmotor for ekvivalensrelasjoner.
# 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))Håndtering av spesialtilfeller
Viktige spesialtilfeller ved kontosammenslåing:
- Kontoer med én e-postadresse: En konto med bare én e-postadresse danner sin egen komponent, med mindre en annen konto deler denne e-postadressen.
- Samme navn, forskjellige personer: At «John» forekommer i to kontoer, betyr ikke at de tilhører samme person – bare delte e-postadresser slår sammen kontoer. Navnet lagres per e-postadresse, ikke per komponent.
- Tomme kontoer: En konto uten e-postadresser bør hoppes over for å unngå indeksfeil.
Kontroller alltid at løsningen håndterer kontoer som ikke skal slås sammen bare fordi de har samme navn. DSU-forbindelsene styres utelukkende av delte e-postadresser.
# 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)Antall sammenhengende komponenter i en graf
En beslektet oppgave (LeetCode 323) spør etter antallet sammenhengende komponenter i en urettet graf. Dette er enklere enn kontosammenslåing: initialiser DSU med n noder, behandle alle kanter med union, og tell deretter de ulike røttene.
Den mest konsise måten å telle komponenter på er å opprettholde en count-variabel som starter på n, og redusere den hver gang en vellykket union slår sammen to forskjellige komponenter. Alternativt kan du telle antallet noder i der find(i) == i til slutt.
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 isolatedMinste og største komponent
Når du har DSU med sporing av størrelser, kan du svare på spørsmål som «hvor stor er den største sammenhengende komponenten?» eller «hvor mange komponenter har nøyaktig 3 noder?» på O(n) ved å gå gjennom størrelsesmatrisen for noder som er røtter.
Slike spørsmål forekommer i oppgaver som «finn den største sammenhengende øya» i et rutenett eller «identifiser den minste nettverkspartisjonen». Når alle union-operasjoner er fullført, går du gjennom noder i der find(i) == i (disse er røtter) og undersøker størrelsene deres.
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)])Intervjutips for DSU-problemer
Når du møter et problem med sammenslåing av grupper, tilkoblingsspørringer eller finning av den ekstra kanten, bør du umiddelbart tenke på DSU. I intervjuer bør du nevne begge optimaliseringene (banekomprimering + union etter rang/størrelse) for å vise god dybdeforståelse, selv om en enklere naiv DSU ville vært tilstrekkelig med de gitte begrensningene.
Vanlige feil du bør unngå, er å glemme å håndtere tilfellet der begge endepunktene allerede er koblet sammen (union gjør ingenting), å bruke 0-indeksering og 1-indeksering feil om hverandre, og å ikke sortere resultatet for kontosammenslåing (oppgaven krever sorterte e-postlister). Avklar alltid begrensningene i inndataene før du begynner å kode.
# 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)Hurtigsjekk
Test forståelsen din av konseptene i Data Structures & Algorithms — Coding Interview Prep fra denne leksjonen.
Oppsummering av leksjonen
I denne leksjonen lærte du at: kontosammenslåing er et problem med sammenhengende komponenter, der e-postadresser er noder og kontoer kobler sammen e-postadresser, DSU løser det ved å tilordne e-postadresser heltalls-ID-er, utføre union på ID-ene i hver konto og gruppere etter rot, og den samme DSU-malens for gruppering kan brukes på alle problemer med ekvivalensklasser eller klyngedannelse. Neste gang skifter vi tema til bitmanipulering, med utgangspunkt i de grunnleggende AND-, OR-, XOR-, NOT- og skiftoperatorene.
Lær deg Python med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 30
- Leksjoner
- 120
Ofte stilte spørsmål
Er leksjonen «Kontosammenslåing og sammenhengende komponenter» gratis?
Ja – du kan lese valgfritt 3 av leksjonene i læringsstien DSA Interview Prep, inkludert «Kontosammenslåing og sammenhengende komponenter», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i DSA Interview Prep inneholder totalt 4 leksjoner.
Hva lærer jeg i «Kontosammenslåing og sammenhengende komponenter»?
Gruppér kontoer som deler en e-postadresse, ved å behandle e-postadresser som DSU-noder, og samle deretter alle e-postadresser per komponent for å rekonstruere de sammenslåtte kontoene. Du øver på DSA Interview Prep med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med DSA Interview Prep?
Ingen tidligere erfaring er nødvendig. DSA Interview Prep på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.
Hvor lang tid tar leksjonen «Kontosammenslåing og sammenhengende komponenter»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne DSA Interview Prep-leksjonen?
Ja. Alle DSA Interview Prep-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- DSU med banekomprimering
- Union etter rang og den inverse Ackermann-grensen
- Redundant forbindelse og sykeloppdagelse
- Kontosammenslåing og sammenhengende komponenter