Fusion de comptes et composantes connexes
Regroupez les comptes partageant une adresse e-mail en traitant les e-mails comme des nœuds DSU, puis rassemblez tous les e-mails de chaque composante pour reconstruire les comptes fusionnés.
Fusion de comptes et composantes connexes est une leçon DSA Interview Prep gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage DSA Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours DSA Interview Prep comprend 4 leçons au total.
Problème : fusion de comptes
Le problème de fusion de comptes (LeetCode 721) vous donne une liste de comptes, chacun étant une liste de chaînes dont le premier élément est le nom du compte et les autres sont des adresses e-mail. Deux comptes appartiennent à la même personne s’ils partagent au moins une adresse e-mail. Fusionnez tous les comptes appartenant à la même personne et renvoyez des listes d’adresses e-mail triées.
Il s’agit fondamentalement d’un problème de composantes connexes, où les e-mails sont les nœuds et où un compte commun les relie. DSU est l’outil idéal : effectuez union entre tous les e-mails d’un même compte, puis regroupez les e-mails par composante.
# 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')Associer les e-mails à des ID entiers
DSU fonctionne avec des indices entiers, mais nos nœuds sont des chaînes représentant des adresses e-mail. Nous devons associer chaque e-mail unique à un ID entier. Nous devons également mémoriser le nom associé à chaque e-mail. Utilisez un dictionnaire email_to_id pour attribuer des ID successifs, ainsi que email_to_name pour suivre le nom du compte associé à chaque e-mail.
Chaque e-mail unique reçoit un ID. Si le même e-mail apparaît dans plusieurs comptes, il est associé au même ID — et l’union des ID des e-mails d’un même compte les relie en une seule composante. Le nom associé à l’ID de l’e-mail racine est le nom du compte fusionné.
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]})')Union des e-mails dans chaque compte
Pour chaque compte, nous effectuons union entre les ID de tous les e-mails qui y sont répertoriés. Nous choisissons le premier e-mail du compte comme représentant et effectuons union entre son ID et celui de chaque autre e-mail. Cela relie tous les e-mails du compte au sein d’une seule composante.
Après le traitement de tous les comptes, les e-mails apparus ensemble (directement ou par transitivité grâce aux e-mails partagés entre les comptes) possèdent tous la même racine DSU. Il s’agit de l’étape essentielle qui propage la connexité entre plusieurs comptes.
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)) # FalseRegrouper les e-mails par composante
Une fois toutes les unions effectuées, nous parcourons chaque e-mail, trouvons sa racine DSU et regroupons les e-mails par racine à l’aide d’un dictionnaire de listes. L’ID de la racine devient la clé. Enfin, pour chaque groupe, nous récupérons le nom du compte, trions la liste d’e-mails et plaçons le nom au début.
Le tri des e-mails est requis par le problème : dans un compte fusionné, les e-mails doivent être classés dans l’ordre lexicographique. Le nom peut être récupéré à partir de n’importe quel e-mail du groupe, car tous les e-mails d’une même composante appartiennent à la même personne.
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)Solution complète de fusion de comptes
Voici la solution complète qui combine les trois étapes : construire l’association e-mail-ID, effectuer union entre les e-mails de chaque compte et regrouper les e-mails par racine DSU. La complexité temporelle globale est de O(n × m × alpha(n × m)), où n est le nombre de comptes et m le nombre maximal d’e-mails par compte, soit en pratique O(n × m).
La complexité spatiale est de O(n × m) pour les associations d’e-mails et les tableaux DSU. Cette solution gère correctement la fusion transitive : si le compte A partage l’e-mail X avec le compte B, et que le compte B partage l’e-mail Y avec le compte C, alors A, B et C sont tous fusionnés dans un seul groupe.
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)Alternative BFS/DFS pour la fusion de comptes
Une autre approche consiste à construire un graphe reliant les adresses e-mail aux comptes, où les adresses e-mail sont les nœuds et où les arêtes relient les adresses qui apparaissent dans un même compte. BFS/DFS trouve ensuite chaque composante connexe. Bien que cette approche soit correcte, elle nécessite de construire explicitement le graphe et d’exécuter BFS depuis chaque adresse e-mail non visitée — davantage de code et un raisonnement plus difficile qu’avec DSU.
DSU est plus élégant, car la structure d’ensembles disjoints représente naturellement l’appartenance aux composantes sans nécessiter de liste d’adjacence explicite. BFS n’est préférable ici que si vous devez reconstruire le chemin réel ou la chaîne d’adresses e-mail partagées entre deux comptes.
# 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)Généralisation : composantes connexes d’un graphe
Le modèle de fusion de comptes se généralise à tout problème de composantes connexes avec étiquettes : vous disposez d’un ensemble d’éléments, certains éléments sont déclarés équivalents (reliés), et vous souhaitez regrouper tous les éléments équivalents par transitivité. Parmi les exemples figurent les problèmes de regroupement, les groupes d’amis sur les réseaux sociaux et la détection de doublons dans des enregistrements.
L’algorithme général est toujours le même : (1) attribuer un ID entier à chaque élément, (2) appliquer union aux ID des éléments déclarés équivalents, (3) regrouper les éléments selon leur racine DSU. DSU est essentiellement un moteur de regroupement pour les relations d’équivalence.
# 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))Gestion des cas particuliers
Cas particuliers importants lors de la fusion de comptes :
- Comptes avec une seule adresse e-mail : un compte qui ne contient qu’une seule adresse e-mail forme sa propre composante, sauf si un autre compte partage cette adresse.
- Même nom, personnes différentes : le fait que « John » apparaisse dans deux comptes ne signifie pas qu’il s’agit de la même personne — seuls les e-mails partagés permettent de fusionner les comptes. Le nom est enregistré pour chaque adresse e-mail, et non pour chaque composante.
- Comptes vides : un compte sans adresse e-mail doit être ignoré afin d’éviter les erreurs d’index.
Vérifiez toujours que votre solution ne fusionne pas des comptes uniquement parce qu’ils partagent un nom. Les connexions DSU reposent exclusivement sur les adresses e-mail partagées.
# 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)Nombre de composantes connexes dans un graphe
Un problème connexe (LeetCode 323) demande de compter le nombre de composantes connexes dans un graphe non orienté. Il est plus simple que la fusion de comptes : initialisez DSU avec n nœuds, traitez toutes les arêtes avec union, puis comptez les racines distinctes.
La manière la plus concise de compter les composantes consiste à maintenir une variable count qui commence à n et à la décrémenter chaque fois qu’une union réussie fusionne deux composantes différentes. Vous pouvez aussi compter, à la fin, le nombre de nœuds i tels que find(i) == i.
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 isolatedComposante la plus petite et composante la plus grande
Une fois que vous disposez de DSU avec suivi des tailles, vous pouvez répondre à des questions telles que « quelle est la taille de la plus grande composante connexe ? » ou « combien de composantes comportent exactement 3 nœuds ? » en O(n), en parcourant le tableau des tailles aux nœuds racines.
Ces questions apparaissent dans des problèmes comme « trouver la plus grande île connexe » sur une grille ou « identifier la plus petite partition d’un réseau ». Une fois toutes les unions terminées, recherchez les nœuds i tels que find(i) == i (ce sont les racines) et examinez leurs tailles.
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)])Conseils d’entretien pour les problèmes DSU
Lorsque vous rencontrez un problème de fusion de groupes, de requêtes de connexité ou de recherche de l’arête supplémentaire, pensez immédiatement à DSU. Pendant les entretiens, mentionnez les deux optimisations (compression de chemin + union par rang/taille) pour démontrer votre maîtrise du sujet, même si une version naïve de DSU plus simple suffirait avec les contraintes données.
Erreurs courantes à éviter : oublier de gérer le cas où les deux extrémités sont déjà reliées (union ne fait alors rien), confondre l’indexation à partir de 0 et celle à partir de 1, et ne pas trier la sortie pour la fusion de comptes (le problème exige des listes d’adresses e-mail triées). Clarifiez toujours les contraintes de l’entrée avant de coder.
# 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)Vérification rapide
Évaluez votre compréhension des concepts de Structures de données et algorithmes — Préparation aux entretiens de programmation abordés dans cette leçon.
Récapitulatif de la leçon
Dans cette leçon, vous avez appris que : la fusion de comptes est un problème de composantes connexes où les adresses e-mail sont les nœuds et où les comptes relient les adresses, DSU le résout en associant des adresses e-mail à des ID entiers, en appliquant union aux ID de chaque compte, puis en regroupant les éléments selon leur racine, et le même modèle de regroupement DSU s’applique à tout problème de classe d’équivalence ou de regroupement. Nous passons ensuite à la manipulation des bits, en commençant par les opérateurs fondamentaux AND, OR, XOR, NOT et de décalage.
Apprends Python avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 30
- Leçons
- 120
Questions Fréquemment Posées
La leçon « Fusion de comptes et composantes connexes » est-elle gratuite ?
Oui — le texte complet de « Fusion de comptes et composantes connexes » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours DSA Interview Prep, passe à CoddyKit PRO. Le cours DSA Interview Prep comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Fusion de comptes et composantes connexes » ?
Regroupez les comptes partageant une adresse e-mail en traitant les e-mails comme des nœuds DSU, puis rassemblez tous les e-mails de chaque composante pour reconstruire les comptes fusionnés. Tu pratiques DSA Interview Prep avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer DSA Interview Prep ?
Aucune expérience préalable n'est requise. DSA Interview Prep sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Fusion de comptes et composantes connexes » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon DSA Interview Prep ?
Oui. Chaque leçon DSA Interview Prep inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- DSU avec compression de chemin
- Union par rang et borne de l’inverse d’Ackermann
- Connexion redondante et détection de cycles
- Fusion de comptes et composantes connexes