Accounts Merge och sammanhängande komponenter
Gruppera konton som delar e-post genom att behandla e-postadresser som DSU-noder och samla sedan alla e-postadresser per komponent för att återskapa de sammanslagna kontona.
Accounts Merge och sammanhängande komponenter är en gratis lektion i Förberedelse inför kodningsintervjuer på CoddyKit. Detta är lektion 4 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Förberedelse inför kodningsintervjuer, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.
Problem: Accounts Merge
Problemet Accounts Merge (LeetCode 721) ger en lista med konton, där varje konto är en lista med strängar: det första elementet är kontonamnet och resten är e-postadresser. Två konton tillhör samma person om de delar minst en e-postadress. Slå ihop alla konton som tillhör samma person och returnera sorterade listor med e-postadresser.
Detta är i grunden ett problem med sammanhängande komponenter, där e-postadresserna är noder och en gemensam e-postadress länkar samman dem. DSU är det idealiska verktyget: gör union på alla e-postadresser inom samma konto och samla sedan e-postadresserna 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')Mappa e-postadresser till heltals-ID:n
DSU arbetar med heltalsindex, men våra noder är e-poststrängar. Därför måste varje unik e-postadress mappas till ett heltals-ID. Vi måste också komma ihåg vilket namn som äger varje e-postadress. Använd en dictionary, email_to_id, för att tilldela stigande ID:n och email_to_name för att hålla reda på kontonamnet som hör till varje e-postadress.
Varje unik e-postadress får ett ID. Om samma e-postadress förekommer i flera konton mappas den till samma ID — när ID:na för e-postadresserna inom ett konto förenas med union kopplas de samman till en enda komponent. Namnet som hör till ID:t för e-postadressen i roten blir det sammanslagna kontonamnet.
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ör union på e-postadresserna inom varje konto
För varje konto gör vi union på ID:na för alla e-postadresser som listas tillsammans. Vi väljer den första e-postadressen i kontot som representant och gör union mellan dess ID och ID:t för varje annan e-postadress. På så sätt kopplas alla e-postadresser i kontot till samma komponent.
När alla konton har behandlats delar e-postadresser som förekom tillsammans — direkt eller transitivt via gemensamma e-postadresser mellan konton — samma DSU-rot. Detta är det centrala steget som sprider konnektiviteten mellan flera konton.
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)) # FalseSamla e-postadresser per komponent
När alla union-operationer är klara itererar vi över varje e-postadress, hittar dess DSU-rot och grupperar e-postadresserna efter roten med en dictionary med listor. Rot-ID:t blir nyckeln. Slutligen hämtar vi kontonamnet för varje grupp, sorterar e-postlistan och lägger till namnet först.
Sortering av e-postadresserna krävs av problemet — inom ett sammanslaget konto måste e-postadresserna vara i lexikografisk ordning. Namnet kan hämtas från vilken e-postadress som helst i gruppen, eftersom alla e-postadresser i samma komponent tillhör samma 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)Fullständig lösning på Accounts Merge
Här är den fullständiga lösningen, som kombinerar alla tre stegen: bygg mappningen från e-postadress till ID, gör union på e-postadresserna inom varje konto och samla grupperade e-postadresser efter DSU-rot. Den totala tidskomplexiteten är O(n × m × alpha(n × m)), där n är antalet konton och m är det maximala antalet e-postadresser per konto, vilket i praktiken är O(n × m).
Rymdkomplexiteten är O(n × m) för e-postmappningarna och DSU-arrayerna. Lösningen hanterar den transitiva sammanslagningen korrekt: om konto A delar e-postadressen X med konto B och konto B delar e-postadressen Y med konto C, slås A, B och C ihop till en enda grupp.
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 för kontosammanslagning
En alternativ metod bygger en graf från e-postadresser till konton, där e-postadresser är noder och kanter kopplar samman e-postadresser som förekommer i samma konto. Därefter hittar BFS/DFS varje sammanhängande komponent. Metoden är korrekt, men kräver att grafen byggs uttryckligen och att BFS körs från varje e-postadress som ännu inte besökts – mer kod och svårare att resonera kring än DSU.
DSU är renare eftersom union-find-strukturen naturligt representerar komponenttillhörighet utan att en uttrycklig grannskapslista behöver skapas. BFS är egentligen bara att föredra här om ni behöver återskapa den faktiska vägen eller kedjan av delade e-postadresser mellan två konton.
# 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: Sammanhängande komponenter i grafer
Mönstret för kontosammanslagning kan generaliseras till alla problem med sammanhängande komponenter med etiketter: ni har en mängd objekt, där vissa objekt anges vara ekvivalenta (sammanlänkade), och vill gruppera alla transitivt ekvivalenta objekt tillsammans. Exempel är klustringsproblem, vänskapsgrupper i sociala nätverk och identifiering av dubblettposter.
Den generella algoritmen är alltid: (1) tilldela varje objekt ett heltals-ID, (2) slå ihop ID:n för objekt som deklarerats vara ekvivalenta, (3) gruppera objekten efter deras DSU-rot. DSU är i grunden en grupperingsmotor för ekvivalensrelationer.
# 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))Hantera specialfall
Viktiga specialfall vid kontosammanslagning:
- Konton med en e-postadress: ett konto med endast en e-postadress bildar en egen komponent, såvida inte ett annat konto delar den e-postadressen.
- Samma namn, olika personer: att 'John' förekommer i två konton betyder inte att det är samma person — endast delade e-postadresser slår samman konton. Namnet lagras per e-postadress, inte per komponent.
- Tomma konton: ett konto utan e-postadresser ska hoppas över för att undvika indexfel.
Kontrollera alltid att lösningen hanterar konton som inte ska slås samman bara för att de delar ett namn. DSU-kopplingarna styrs enbart av delade 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)Antal sammanhängande komponenter i en graf
En relaterad uppgift (LeetCode 323) frågar efter antalet sammanhängande komponenter i en oriktad graf. Den är enklare än kontosammanslagning: initiera DSU med n noder, bearbeta alla kanter med union och räkna sedan de distinkta rötterna.
Det mest koncisa sättet att räkna komponenter är att ha en variabel count som börjar på n och minska den varje gång en lyckad union slår samman två olika komponenter. Alternativt kan ni räkna antalet noder i där find(i) == i i slutet.
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 isolatedMinsta och största komponenten
När DSU spårar storleken kan ni besvara frågor som 'hur stor är den största sammanhängande komponenten?' eller 'hur många komponenter har exakt 3 noder?' på O(n)-tid genom att gå igenom storleksarrayen vid rotnoder.
Sådana frågor förekommer i problem som 'hitta den största sammanhängande ön' i ett rutnät eller 'identifiera den minsta nätverkspartitionen'. När alla unioner är klara går ni igenom noder där find(i) == i (dessa är rötter) och granskar deras storlekar.
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 för DSU-problem
När ni stöter på ett problem med sammanslagning av grupper, anslutningsfrågor eller att hitta den extra kanten, tänk genast på DSU. Under intervjuer bör ni nämna båda optimeringarna (sökvägskomprimering + union efter rang/storlek) för att visa djupa kunskaper, även om en enklare naiv DSU skulle klara sig med de givna begränsningarna.
Vanliga misstag att undvika är att glömma hantera fallet där båda ändpunkterna redan är anslutna (union gör ingenting), att blanda ihop 0-baserad och 1-baserad indexering samt att inte sortera utdata för kontosammanslagning (problemet kräver sorterade e-postlistor). Klargör alltid indatabegränsningarna innan ni börjar koda.
# 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)Snabbtest
Testa er förståelse av begreppen Data Structures & Algorithms — Coding Interview Prep från den här lektionen.
Lektionssammanfattning
I den här lektionen lärde ni er: accounts-merge är ett problem med sammanhängande komponenter där e-postadresser är noder och konton länkar samman e-postadresser, DSU löser det genom att mappa e-postadresser till heltals-ID:n, slå ihop ID:n inom varje konto och gruppera efter rot, samt samma DSU-mall för gruppering kan användas för alla problem med ekvivalensklasser eller klustring. Nästa steg är bitmanipulering, med början i de grundläggande operatorerna AND, OR, XOR, NOT och skiftoperatorerna.
Lär dig Förberedelse inför kodningsintervjuer med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 90
- Lektioner
- 360
Vanliga frågor
Är lektionen ”Accounts Merge och sammanhängande komponenter” gratis?
Ja – hela texten till ”Accounts Merge och sammanhängande komponenter” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Förberedelse inför kodningsintervjuer, kan Ni uppgradera till CoddyKit PRO. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.
Vad lär jag mig i ”Accounts Merge och sammanhängande komponenter”?
Gruppera konton som delar e-post genom att behandla e-postadresser som DSU-noder och samla sedan alla e-postadresser per komponent för att återskapa de sammanslagna kontona. Ni övar på Förberedelse inför kodningsintervjuer med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Förberedelse inför kodningsintervjuer?
Du behöver inga förkunskaper. Utbildningen i Förberedelse inför kodningsintervjuer på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.
Hur lång tid tar lektionen ”Accounts Merge och sammanhängande komponenter”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Förberedelse inför kodningsintervjuer-lektionen?
Ja. Varje Förberedelse inför kodningsintervjuer-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- DSU med path compression
- Union by rank och den inversa Ackermann-gränsen
- Redundant Connection och cykeldetektering
- Accounts Merge och sammanhängande komponenter