Penggabungan Akun dan Komponen Terhubung
Kelompokkan akun yang berbagi email dengan memperlakukan email sebagai simpul DSU, lalu kumpulkan semua email per komponen untuk menyusun ulang akun gabungan
Penggabungan Akun dan Komponen Terhubung adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Masalah: Penggabungan Akun
Masalah Penggabungan Akun (LeetCode 721) memberi Anda daftar akun, yang masing-masing berupa daftar teks dengan elemen pertama berupa nama akun dan sisanya berupa alamat surel. Dua akun dimiliki orang yang sama jika keduanya memiliki setidaknya satu alamat surel yang sama. Gabungkan semua akun milik orang yang sama dan kembalikan daftar alamat surel yang telah diurutkan.
Ini pada dasarnya adalah masalah komponen terhubung, dengan alamat surel sebagai simpul dan akun yang sama menghubungkannya. DSU adalah alat yang ideal: lakukan union pada semua alamat surel dalam akun yang sama, lalu kumpulkan alamat surel untuk 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 Alamat Surel ke ID
DSU bekerja dengan indeks bilangan bulat, tetapi simpul kita berupa teks alamat surel. Kita perlu memetakan setiap alamat surel unik ke ID bilangan bulat. Kita juga perlu mengingat nama pemilik setiap alamat surel. Gunakan kamus email_to_id untuk menetapkan ID yang terus bertambah, dan email_to_name untuk melacak nama akun yang terkait dengan setiap alamat surel.
Setiap alamat surel unik memperoleh satu ID. Jika alamat surel yang sama muncul dalam beberapa akun, alamat tersebut dipetakan ke ID yang sama — dan operasi union pada ID alamat surel dalam satu akun menghubungkannya menjadi satu komponen. Nama yang terkait dengan ID alamat surel akar menjadi nama akun gabungan.
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]})')Melakukan Union pada Alamat Surel dalam Setiap Akun
Untuk setiap akun, kita melakukan union pada ID semua alamat surel yang tercantum bersama. Kita memilih alamat surel pertama dalam akun sebagai wakil, lalu melakukan union pada ID setiap alamat surel lainnya dengan ID tersebut. Dengan demikian, semua alamat surel dalam akun terhubung dalam satu komponen.
Setelah semua akun diproses, alamat surel yang muncul bersama (secara langsung atau transitif melalui alamat surel yang sama di berbagai akun) semuanya memiliki akar DSU yang sama. Inilah langkah penting yang menyebarkan konektivitas ke berbagai akun.
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 Alamat Surel per Komponen
Setelah semua operasi union selesai, kita mengiterasi setiap alamat surel, menemukan akar DSU-nya, lalu mengelompokkan alamat surel berdasarkan akar tersebut menggunakan kamus berisi daftar. ID akar menjadi kuncinya. Terakhir, untuk setiap kelompok, kita mengambil nama akun, melakukan sort terhadap daftar alamat surel, lalu menempatkan nama di awal.
Sort alamat surel diwajibkan oleh masalah ini — dalam akun gabungan, alamat surel harus berada dalam urutan leksikografis. Nama dapat diambil dari alamat surel mana pun dalam kelompok tersebut karena semua alamat surel dalam satu komponen dimiliki 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)Solusi Lengkap Penggabungan Akun
Berikut solusi lengkap yang menggabungkan ketiga langkah: membangun pemetaan alamat surel-ke-ID, melakukan union pada alamat surel dalam setiap akun, dan mengumpulkan alamat surel yang dikelompokkan berdasarkan akar DSU. Kompleksitas waktu keseluruhan adalah O(n × m × alpha(n × m)), dengan n sebagai jumlah akun dan m sebagai jumlah maksimum alamat surel per akun, yang secara efektif sama dengan O(n × m).
Kompleksitas ruang adalah O(n × m) untuk pemetaan alamat surel dan larik DSU. Solusi ini menangani penggabungan transitif dengan benar: jika akun A memiliki alamat surel X yang sama dengan akun B, dan akun B memiliki alamat surel Y yang sama dengan akun C, maka A, B, dan C semuanya digabungkan menjadi satu kelompok.
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 Akun
Pendekatan alternatif membangun graf email-ke-akun, dengan email sebagai simpul dan sisi yang menghubungkan email yang muncul dalam akun yang sama. Kemudian BFS/DFS menemukan setiap komponen terhubung. Meskipun benar, pendekatan ini mengharuskan Anda membangun graf secara eksplisit dan menjalankan BFS dari setiap email yang belum dikunjungi—kode yang lebih banyak dan penalaran yang lebih sulit dibandingkan DSU.
DSU lebih rapi karena strukturnya secara alami merepresentasikan keanggotaan komponen tanpa memerlukan daftar ketetanggaan eksplisit. BFS hanya lebih disarankan di sini jika Anda perlu merekonstruksi jalur atau rantai email bersama yang sebenarnya antara dua akun.
# 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 Terhubung pada Graf
Pola penggabungan akun dapat digeneralisasikan ke masalah komponen terhubung dengan label apa pun: Anda memiliki sekumpulan item, beberapa item dinyatakan ekuivalen (terhubung), dan Anda ingin mengelompokkan semua item yang ekuivalen secara transitif. Contohnya mencakup masalah pengelompokan, kelompok teman di jejaring sosial, dan pendeteksian rekaman duplikat.
Algoritma umumnya selalu sama: (1) tetapkan ID bilangan bulat untuk setiap item, (2) lakukan union pada ID item yang dinyatakan ekuivalen, (3) kelompokkan item berdasarkan akar DSU-nya. DSU pada dasarnya adalah mesin pengelompokan untuk relasi ekuivalensi.
# 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))Menangani Kasus Tepi
Kasus tepi penting dalam penggabungan akun:
- Akun dengan satu email: akun yang hanya memiliki satu email membentuk komponennya sendiri, kecuali ada akun lain yang menggunakan email tersebut.
- Nama sama, orang berbeda: kemunculan 'John' dalam dua akun tidak berarti keduanya adalah orang yang sama—hanya email yang sama yang menggabungkan akun. Nama disimpan untuk setiap email, bukan untuk setiap komponen.
- Akun kosong: akun tanpa email harus dilewati untuk menghindari kesalahan indeks.
Selalu verifikasi bahwa solusi Anda menangani akun yang seharusnya tidak digabungkan hanya karena memiliki nama yang sama. Koneksi DSU semata-mata ditentukan oleh alamat email yang sama.
# 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)Jumlah Komponen Terhubung dalam Graf
Masalah terkait (LeetCode 323) meminta jumlah komponen terhubung dalam graf tak berarah. Masalah ini lebih sederhana daripada penggabungan akun: inisialisasi DSU dengan n simpul, proses semua sisi menggunakan union, lalu hitung akar yang berbeda.
Cara paling ringkas untuk menghitung komponen adalah mempertahankan variabel count yang dimulai dari n, lalu menguranginya setiap kali union yang berhasil menggabungkan dua komponen berbeda. Sebagai alternatif, hitung jumlah simpul 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 memiliki DSU dengan pelacakan ukuran, Anda dapat menjawab pertanyaan seperti 'berapa ukuran komponen terhubung terbesar?' atau 'berapa banyak komponen yang tepat memiliki 3 simpul?' dalam O(n) dengan memindai larik ukuran pada simpul-simpul akar.
Pertanyaan ini muncul dalam masalah seperti 'menemukan pulau terhubung terbesar' pada kisi atau 'mengidentifikasi partisi jaringan terkecil'. Setelah semua union selesai, cari simpul i yang memenuhi find(i) == i (simpul-simpul ini adalah akar), lalu periksa ukurannya.
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)])Tips Wawancara untuk Soal DSU
Ketika menemukan masalah yang melibatkan penggabungan kelompok, kueri keterhubungan, atau pencarian sisi tambahan, segera pikirkan DSU. Saat wawancara, sebutkan kedua optimisasi tersebut (kompresi jalur + union berdasarkan peringkat/ukuran) untuk menunjukkan pemahaman yang mendalam, meskipun DSU sederhana mungkin sudah lolos dengan batasan yang diberikan.
Kesalahan umum yang harus dihindari: lupa menangani kasus ketika kedua titik ujung sudah terhubung (union tidak melakukan apa-apa), keliru menggunakan pengindeksan mulai 0 atau mulai 1, dan tidak mengurutkan hasil untuk penggabungan akun (masalah ini mengharuskan daftar email diurutkan). Selalu klarifikasi batasan masukan sebelum menulis 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)Pemeriksaan Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari bahwa: penggabungan akun adalah masalah komponen terhubung dengan email sebagai simpul dan akun yang menghubungkan email, DSU menyelesaikannya dengan memetakan email ke ID bilangan bulat, melakukan union pada ID dalam setiap akun, dan mengelompokkannya berdasarkan akar, serta templat pengelompokan DSU yang sama berlaku untuk masalah kelas ekuivalensi atau pengelompokan apa pun. Berikutnya, kita beralih ke manipulasi bit, dimulai dengan operator AND, OR, XOR, NOT, dan geser dasar.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Penggabungan Akun dan Komponen Terhubung” gratis?
Ya — teks lengkap “Penggabungan Akun dan Komponen Terhubung” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Penggabungan Akun dan Komponen Terhubung”?
Kelompokkan akun yang berbagi email dengan memperlakukan email sebagai simpul DSU, lalu kumpulkan semua email per komponen untuk menyusun ulang akun gabungan Kamu berlatih DSA Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai DSA Interview Prep?
Tidak diperlukan pengalaman sebelumnya. DSA Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.
Berapa lama pelajaran “Penggabungan Akun dan Komponen Terhubung” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran DSA Interview Prep ini?
Ya. Setiap pelajaran DSA Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- DSU dengan Kompresi Jalur
- Union Berdasarkan Rank dan Batas Invers Ackermann
- Koneksi Berlebih dan Deteksi Siklus
- Penggabungan Akun dan Komponen Terhubung