0Pricing
DSA Interview Prep · Ders

Hesapları Birleştirme ve Bağlantılı Bileşenler

E-posta paylaşan hesapları e-postaları DSU düğümleri olarak ele alarak gruplayın; ardından birleştirilmiş hesapları yeniden oluşturmak için her bileşendeki tüm e-postaları toplayın.

Hesapları Birleştirme ve Bağlantılı Bileşenler, CoddyKit'te ücretsiz bir DSA Interview Prep dersidir. Bu, 4 dersinin 4. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, DSA Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. DSA Interview Prep kursu toplamda 4 dersten oluşur.

Problem: Hesapları Birleştirme

Hesapları Birleştirme problemi (LeetCode 721), her biri dizelerden oluşan bir liste olan hesaplar listesini verir; ilk öğe hesap adı, geri kalanlar e-posta adresleridir. İki hesap en az bir e-postayı paylaşıyorsa aynı kişiye aittir. Aynı kişiye ait tüm hesapları birleştirin ve sıralanmış e-posta listelerini döndürün.

Bu, temelde e-postaların düğüm, ortak hesabın ise onları bağlayan unsur olduğu bir bağlantılı bileşenler problemidir. DSU ideal araçtır: aynı hesap içindeki tüm e-postaları union edin, ardından her bileşendeki e-postaları toplayın.

# 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')

E-postaları Tamsayı ID'lerine Eşleme

DSU tamsayı indisleri üzerinde çalışır, ancak bizim düğümlerimiz e-posta dizeleridir. Her benzersiz e-postayı bir tamsayı ID'ye eşlememiz gerekir. Ayrıca her e-postanın hangi ada ait olduğunu hatırlamalıyız. Artan ID'ler atamak için email_to_id sözlüğünü, her e-postayla ilişkili hesap adını izlemek için de email_to_name sözlüğünü kullanın.

Her benzersiz e-posta bir ID alır. same e-posta birden fazla hesapta görünürse aynı ID'ye eşlenir — bir hesap içindeki e-postaların ID'leri üzerinde union yapmak, bunları tek bir bileşende birbirine bağlar. Kök e-postanın ID'siyle ilişkilendirilmiş ad, birleştirilmiş hesap adıdır.

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]})')

Her Hesaptaki E-postaları union Etme

Her hesap için, birlikte listelenen tüm e-postaların ID'leri üzerinde union uygularız. Hesaptaki ilk e-postayı temsilci olarak seçer ve diğer her e-postanın ID'sini onunla union ederiz. Böylece hesaptaki tüm e-postalar tek bir bileşene bağlanır.

Tüm hesaplar işlendikten sonra, birlikte görünen e-postalar (doğrudan veya hesaplar arasındaki ortak e-postalar üzerinden geçişli olarak) same DSU kökünü paylaşır. Bu, bağlantılılığın birden çok hesap boyunca yayılmasını sağlayan temel adımdır.

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))  # False

Bileşen Başına E-postaları Toplama

Tüm union işlemleri tamamlandıktan sonra her e-postayı dolaşır, DSU kökünü bulur ve bir liste sözlüğü kullanarak e-postaları bu köke göre gruplandırırız. Kök ID anahtar olur. Son olarak her grup için hesap adını alır, e-posta listesini sort eder ve adı listenin başına ekleriz.

E-postaları sort etmek problem gereğidir — birleştirilmiş hesap içindeki e-postalar sözlük sırasına göre olmalıdır. Gruptaki herhangi bir e-postadan ad alınabilir; çünkü tek bir bileşendeki tüm e-postalar same kişiye aittir.

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)

Eksiksiz Hesapları Birleştirme Çözümü

İşte üç adımı birleştiren eksiksiz çözüm: e-posta-ID eşlemesini oluşturmak, her hesap içindeki e-postaları union etmek ve gruplandırılmış e-postaları DSU köküne göre toplamak. Genel zaman karmaşıklığı, n hesap ve hesap başına en fazla m e-posta için O(n × m × alpha(n × m))'dir; bu pratikte O(n × m) olur.

Alan karmaşıklığı, e-posta eşlemeleri ve DSU dizileri için O(n × m)'dir. Bu çözüm geçişli birleştirmeyi doğru biçimde ele alır: A hesabı B hesabıyla X e-postasını, B hesabı da C hesabıyla Y e-postasını paylaşıyorsa A, B ve C tek bir grupta birleştirilir.

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)

Hesapları Birleştirmek İçin BFS/DFS Alternatifi

Alternatif bir yaklaşım, e-postaların düğüm olduğu ve aynı hesapta yer alan e-postaları kenarların birbirine bağladığı bir e-posta-hesap grafiği oluşturur. Ardından BFS/DFS, bağlantılı bileşenlerin her birini bulur. Bu yaklaşım doğru olsa da grafiği açıkça oluşturmayı ve ziyaret edilmemiş her e-postadan BFS başlatmayı gerektirir; bu da DSU'ya kıyasla daha fazla kod ve daha zor bir akıl yürütme süreci demektir.

DSU daha temizdir; çünkü birleşim-bulma yapısı, açık bir komşuluk listesine ihtiyaç duymadan bileşen üyeliğini doğal olarak temsil eder. Burada BFS'nin tercih edilmesi gereken tek durum, iki hesap arasındaki paylaşılan e-postaların gerçek yolunu veya zincirini yeniden oluşturmanız gerektiğidir.

# 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)

Genelleme: Grafikteki Bağlantılı Bileşenler

Hesap birleştirme örüntüsü, etiketli bağlantılı bileşenler içeren her probleme genellenebilir: bir öğe kümeniz vardır, bazı öğelerin eşdeğer (bağlantılı) olduğu belirtilmiştir ve geçişli olarak eşdeğer olan tüm öğeleri birlikte gruplamak istersiniz. Kümeleme problemleri, sosyal ağlardaki arkadaş grupları ve yinelenen kayıtların tespiti buna örnektir.

Genel algoritma her zaman şöyledir: (1) her öğeye bir tamsayı ID atayın, (2) eşdeğer olduğu belirtilen öğelerin ID'lerini birleştirin, (3) öğeleri DSU köklerine göre gruplayın. DSU, eşdeğerlik ilişkileri için temel olarak bir gruplama motorudur.

# 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))

Kenar Durumlarını Ele Alma

Hesapları birleştirirken önemli kenar durumları:

  • Tek e-postalı hesaplar: yalnızca bir e-posta içeren bir hesap, başka bir hesap bu e-postayı paylaşmadığı sürece kendi bileşenini oluşturur.
  • Aynı ad, farklı kişiler: İki hesapta 'John' adının geçmesi, bunların aynı kişi olduğu anlamına gelmez; hesapları yalnızca ortak e-postalar birleştirir. Ad, bileşen başına değil, e-posta başına saklanır.
  • Boş hesaplar: dizin hatalarını önlemek için e-posta içermeyen bir hesap atlanmalıdır.

Yalnızca aynı adı paylaştıkları için birleştirilmemesi gereken hesapları çözümünüzün doğru şekilde ele aldığını her zaman doğrulayın. DSU bağlantıları yalnızca ortak e-posta adresleriyle oluşturulur.

# 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)

Grafikteki Bağlantılı Bileşenlerin Sayısı

İlişkili bir problem olan LeetCode 323, yönsüz bir grafikteki bağlantılı bileşenlerin sayısını sorar. Bu, hesapları birleştirme probleminden daha basittir: DSU'yu n düğümle başlatın, tüm kenarları union ile işleyin ve ardından farklı kökleri sayın.

Bileşenleri saymanın en kısa yolu, n'den başlayan bir count değişkeni tutmak ve başarılı her union işlemi iki farklı bileşeni birleştirdiğinde bu değişkeni bir azaltmaktır. Alternatif olarak, sonunda find(i) == i koşulunu sağlayan düğümlerin i sayısını hesaplayabilirsiniz.

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 isolated

En Küçük ve En Büyük Bileşen

Boyut takibi yapan bir DSU'nuz olduğunda, 'en büyük bağlantılı bileşenin boyutu nedir?' veya 'tam olarak 3 düğüm içeren kaç bileşen var?' gibi sorguları, kök düğümlerdeki boyut dizisini tarayarak O(n) zamanında yanıtlayabilirsiniz.

Bu sorgular, ızgara üzerindeki 'en büyük bağlantılı adayı bulma' veya 'en küçük ağ bölümünü belirleme' gibi problemlerde karşınıza çıkar. Tüm birleştirmeler tamamlandıktan sonra find(i) == i koşulunu sağlayan düğümleri tarayın; bunlar köklerdir ve boyutlarını inceleyin.

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)])

DSU Problemleri İçin Mülakat İpuçları

Grupları birleştirme, bağlantılılık sorguları veya fazladan kenarı bulma içeren bir problemle karşılaştığınızda hemen DSU'yu düşünün. Mülakatlarda, daha basit ve saf bir DSU verilen kısıtlar altında yeterli olsa bile, konuya hâkimiyetinizi göstermek için her iki iyileştirmeden de (yol sıkıştırma + dereceye/boyuta göre birleştirme) söz edin.

Kaçınmanız gereken yaygın hatalar şunlardır: her iki uç noktanın zaten bağlı olduğu durumu ele almayı unutmak (union hiçbir işlem yapmaz), 0 tabanlı ve 1 tabanlı dizinlemeyi yanlış kullanmak ve hesapları birleştirme probleminde çıktıyı sıralamamak (problem, sıralanmış e-posta listeleri gerektirir). Kod yazmaya başlamadan önce girdi kısıtlarını her zaman netleştirin.

# 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)

Kısa Kontrol

Bu dersteki Veri Yapıları ve Algoritmalar — Kodlama Mülakatına Hazırlık kavramlarını anlayıp anlamadığınızı test edin.

Ders Özeti

Bu derste şunları öğrendiniz: accounts-merge, e-postaların düğüm ve hesapların e-postaları birbirine bağladığı bir bağlantılı bileşen problemidir; DSU, e-postaları tamsayı ID'lerle eşleyerek, her hesap içindeki ID'leri birleştirerek ve köke göre gruplayarak bu problemi çözer; ayrıca aynı DSU gruplama şablonu, her eşdeğerlik sınıfı veya kümeleme problemine uygulanabilir. Sırada, temel AND, OR, XOR, NOT ve kaydırma işleçleriyle başlayacağımız bit işlemleri var.

Sıkça Sorulan Sorular

“Hesapları Birleştirme ve Bağlantılı Bileşenler” dersi ücretsiz mi?

Evet — “Hesapları Birleştirme ve Bağlantılı Bileşenler” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve DSA Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. DSA Interview Prep kursu toplamda 4 dersten oluşur.

“Hesapları Birleştirme ve Bağlantılı Bileşenler” dersinde ne öğreneceğim?

E-posta paylaşan hesapları e-postaları DSU düğümleri olarak ele alarak gruplayın; ardından birleştirilmiş hesapları yeniden oluşturmak için her bileşendeki tüm e-postaları toplayın. DSA Interview Prep ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

DSA Interview Prep öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te DSA Interview Prep, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 4. dersidir.

“Hesapları Birleştirme ve Bağlantılı Bileşenler” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu DSA Interview Prep dersinde kod yazıp çalıştırabilir miyim?

Evet. Her DSA Interview Prep dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. Yol Sıkıştırmalı DSU
  2. Dereceye Göre Birleştirme ve Ters Ackermann Sınırı
  3. Gereksiz Bağlantı ve Çevrim Belirleme
  4. Hesapları Birleştirme ve Bağlantılı Bileşenler
← DSA Interview Prep Sayfasına Dön