Penggabungan Akaun dan Komponen Terhubung
Kumpulkan akaun yang berkongsi e-mel dengan menganggap e-mel sebagai nod DSU, kemudian kumpulkan semua e-mel bagi setiap komponen untuk membina semula akaun yang digabungkan.
Penggabungan Akaun dan Komponen Terhubung ialah pelajaran Persediaan Temu Duga Pengaturcaraan percuma di CoddyKit. Ini ialah pelajaran 4 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Persediaan Temu Duga Pengaturcaraan, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Persediaan Temu Duga Pengaturcaraan merangkumi sejumlah 4 pelajaran.
Masalah: Penggabungan Akaun
Masalah Penggabungan Akaun (LeetCode 721) memberikan senarai akaun, yang setiap satunya ialah senarai rentetan dengan elemen pertama sebagai nama akaun dan elemen selebihnya sebagai alamat e-mel. Dua akaun dimiliki oleh orang yang sama jika kedua-duanya berkongsi sekurang-kurangnya satu e-mel. Gabungkan semua akaun yang dimiliki oleh orang yang sama dan kembalikan senarai e-mel yang telah diisih.
Pada asasnya, ini ialah masalah komponen terhubung dengan e-mel sebagai nod dan akaun yang dikongsi menghubungkan e-mel tersebut. DSU ialah alat yang ideal: lakukan union pada semua e-mel dalam akaun yang sama, kemudian kumpulkan e-mel bagi setiap komponen.
# 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')Memetakan E-mel kepada ID Integer
DSU berfungsi pada indeks integer, tetapi nod kita ialah rentetan e-mel. Kita perlu memetakan setiap e-mel unik kepada ID integer. Kita juga perlu mengingati nama yang memiliki setiap e-mel. Gunakan kamus email_to_id untuk menetapkan ID yang meningkat secara berurutan, dan email_to_name untuk menjejaki nama akaun yang dikaitkan dengan setiap e-mel.
Setiap e-mel unik mendapat satu ID. Jika e-mel yang sama muncul dalam beberapa akaun, e-mel itu dipetakan kepada ID yang sama — dan union terhadap ID e-mel dalam satu akaun menghubungkan e-mel tersebut menjadi satu komponen. Nama yang dikaitkan dengan ID e-mel akar ialah nama akaun yang digabungkan.
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 E-mel dalam Setiap Akaun
Bagi setiap akaun, kita melakukan union terhadap ID semua e-mel yang disenaraikan bersama. Kita memilih e-mel pertama dalam akaun sebagai wakil, lalu melakukan union antara ID setiap e-mel lain dengan ID tersebut. Ini menghubungkan semua e-mel dalam akaun kepada satu komponen.
Selepas semua akaun diproses, e-mel yang muncul bersama-sama (secara langsung atau transitif melalui e-mel yang dikongsi merentas akaun) semuanya berkongsi akar DSU yang sama. Inilah langkah penting yang menyebarkan keterhubungan merentas beberapa akaun.
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)) # FalseMengumpulkan E-mel bagi Setiap Komponen
Selepas semua union selesai, kita melelar setiap e-mel, mencari akar DSU-nya dan mengumpulkan e-mel mengikut akar tersebut menggunakan kamus senarai. ID akar menjadi kunci. Akhir sekali, bagi setiap kumpulan, kita mendapatkan nama akaun, sort senarai e-mel dan meletakkan nama di hadapan.
Pengisihan e-mel diperlukan oleh masalah ini — dalam akaun yang digabungkan, e-mel mesti berada dalam susunan leksikografi. Nama boleh didapatkan daripada mana-mana e-mel dalam kumpulan tersebut (semua e-mel dalam satu komponen dimiliki oleh orang yang sama).
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)Penyelesaian Lengkap Penggabungan Akaun
Berikut ialah penyelesaian lengkap yang menggabungkan ketiga-tiga langkah: bina pemetaan e-mel kepada ID, lakukan union terhadap e-mel dalam setiap akaun dan kumpulkan e-mel mengikut akar DSU. Kerumitan masa keseluruhan ialah O(n × m × alpha(n × m)), dengan n sebagai bilangan akaun dan m sebagai bilangan maksimum e-mel bagi setiap akaun, yang secara berkesan ialah O(n × m).
Kerumitan ruang ialah O(n × m) untuk pemetaan e-mel dan tatasusunan DSU. Penyelesaian ini mengendalikan penggabungan transitif dengan betul: jika akaun A berkongsi e-mel X dengan akaun B, dan akaun B berkongsi e-mel Y dengan akaun C, maka A, B dan C semuanya digabungkan menjadi satu kumpulan.
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)Alternatif BFS/DFS untuk Penggabungan Akaun
Pendekatan alternatif membina graf yang memetakan e-mel kepada akaun, dengan e-mel sebagai nod dan sisi yang menghubungkan e-mel yang muncul dalam akaun yang sama. Kemudian, BFS/DFS mencari setiap komponen bersambung. Walaupun pendekatan ini betul, anda perlu membina graf secara eksplisit dan menjalankan BFS daripada setiap e-mel yang belum dilawati — kodnya lebih banyak dan lebih sukar dihuraikan berbanding DSU.
DSU lebih kemas kerana struktur gabung-cari secara semula jadi mewakili keahlian komponen tanpa memerlukan senarai kejiranan yang eksplisit. BFS hanya lebih sesuai di sini jika anda perlu membina semula laluan sebenar atau rantaian e-mel yang dikongsi antara dua akaun.
# 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)Generalisasi: Komponen Bersambung Graf
Pola penggabungan akaun boleh digeneralisasikan kepada sebarang masalah komponen-bersambung-dengan-label: anda mempunyai satu set item, sesetengah item diisytiharkan setara (bersambung), dan anda mahu mengumpulkan semua item yang setara secara transitif bersama-sama. Contohnya termasuk masalah pengelompokan, kumpulan rakan rangkaian sosial dan pengesanan rekod pendua.
Algoritma umum sentiasa sama: (1) berikan ID integer kepada setiap item, (2) gabungkan ID bagi item yang diisytiharkan setara, (3) kumpulkan item mengikut akar DSU. DSU pada asasnya ialah enjin pengelompokan untuk hubungan kesetaraan.
# 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))Mengendalikan Kes Khas
Kes khas penting dalam penggabungan akaun:
- Akaun dengan satu e-mel: akaun yang hanya mempunyai satu e-mel membentuk komponennya sendiri melainkan akaun lain berkongsi e-mel tersebut.
- Nama sama, orang berbeza: 'John' yang muncul dalam dua akaun tidak bermaksud mereka orang yang sama — hanya e-mel yang dikongsi akan menggabungkan akaun. Nama disimpan bagi setiap e-mel, bukan bagi setiap komponen.
- Akaun kosong: akaun tanpa e-mel hendaklah dilangkau untuk mengelakkan ralat indeks.
Sentiasa sahkan bahawa penyelesaian anda mengendalikan akaun yang tidak sepatutnya digabungkan hanya kerana berkongsi nama. Sambungan DSU ditentukan semata-mata oleh alamat e-mel yang dikongsi.
# 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)Bilangan Komponen Bersambung dalam Graf
Masalah berkaitan (LeetCode 323) meminta bilangan komponen bersambung dalam graf tidak berarah. Masalah ini lebih mudah daripada penggabungan akaun: mulakan DSU dengan n nod, proses semua sisi menggunakan union, kemudian kira akar yang berbeza.
Cara paling ringkas untuk mengira komponen adalah dengan mengekalkan pemboleh ubah count yang bermula pada n dan mengurangkannya setiap kali union yang berjaya menggabungkan dua komponen berbeza. Sebagai alternatif, kira bilangan nod i yang memenuhi find(i) == i pada akhir proses.
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 isolatedKomponen Terkecil dan Komponen Terbesar
Setelah mempunyai DSU dengan penjejakan saiz, anda boleh menjawab pertanyaan seperti 'apakah saiz komponen bersambung terbesar?' atau 'berapa banyak komponen yang mempunyai tepat 3 nod?' dalam O(n) dengan mengimbas tatasusunan saiz pada nod akar.
Pertanyaan ini muncul dalam masalah seperti 'cari pulau bersambung terbesar' pada grid atau 'kenal pasti pemisah rangkaian terkecil'. Setelah semua penggabungan selesai, imbas nod i yang memenuhi find(i) == i (ini ialah akar) dan periksa saiznya.
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)])Petua Temu Duga untuk Masalah DSU
Apabila anda menemui masalah yang melibatkan penggabungan kumpulan, pertanyaan kesalinghubungan atau mencari sisi tambahan, fikirkan DSU dengan segera. Semasa temu duga, nyatakan kedua-dua pengoptimuman (pemampatan laluan + penggabungan mengikut pangkat/saiz) untuk menunjukkan pemahaman yang mendalam, walaupun DSU naif yang lebih ringkas akan berjaya dengan kekangan yang diberikan.
Kesilapan lazim yang perlu dielakkan: terlupa mengendalikan keadaan apabila kedua-dua titik hujung sudah bersambung (union tidak melakukan apa-apa), tersalah menggunakan pengindeksan bermula pada 0 berbanding 1, dan tidak mengisih keluaran untuk penggabungan akaun (masalah ini memerlukan senarai e-mel yang diisih). Sentiasa jelaskan kekangan masukan sebelum menulis kod.
# 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)Semak Pantas
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persediaan Temu Duga Pengekodan daripada pelajaran ini.
Rumusan Pelajaran
Dalam pelajaran ini, Anda telah mempelajari: penggabungan akaun ialah masalah komponen bersambung yang menjadikan e-mel sebagai nod dan akaun sebagai penghubung e-mel, DSU menyelesaikannya dengan memetakan e-mel kepada ID integer, menggabungkan ID dalam setiap akaun dan mengumpulkan item mengikut akar, serta templat pengelompokan DSU yang sama boleh digunakan untuk sebarang masalah kelas kesetaraan atau pengelompokan. Seterusnya, kita beralih kepada manipulasi bit, bermula dengan pengendali asas AND, OR, XOR, NOT dan anjakan.
Pelajari Persediaan Temu Duga Pengaturcaraan dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 90
- Pelajaran
- 360
Soalan Lazim
Adakah pelajaran “Penggabungan Akaun dan Komponen Terhubung” percuma?
Ya — teks penuh “Penggabungan Akaun dan Komponen Terhubung” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus Persediaan Temu Duga Pengaturcaraan, tingkat taraf kepada CoddyKit PRO. Kursus Persediaan Temu Duga Pengaturcaraan merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Penggabungan Akaun dan Komponen Terhubung”?
Kumpulkan akaun yang berkongsi e-mel dengan menganggap e-mel sebagai nod DSU, kemudian kumpulkan semua e-mel bagi setiap komponen untuk membina semula akaun yang digabungkan. Anda berlatih Persediaan Temu Duga Pengaturcaraan menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Persediaan Temu Duga Pengaturcaraan?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Persediaan Temu Duga Pengaturcaraan di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 4 daripada 4.
Berapa lamakah pelajaran “Penggabungan Akaun dan Komponen Terhubung” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Persediaan Temu Duga Pengaturcaraan ini?
Ya. Setiap pelajaran Persediaan Temu Duga Pengaturcaraan menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- DSU dengan Pemampatan Laluan
- Penyatuan Mengikut Pangkat dan Had Ackermann Songsang
- Sambungan Berlebihan dan Pengesanan Kitaran
- Penggabungan Akaun dan Komponen Terhubung