🧩 4.6: Linked List Algoritmaları

Klasik Mülakat Soruları ve Çözümleri

🔄

1. Listeyi Tersine Çevir

Easy LeetCode #206

Problem: Verilen linked list'i yerinde (in-place) tersine çevir. "Yerinde" demek, yeni bir liste oluşturmadan mevcut node'ların bağlantılarını değiştirmek demektir.

Neden önemli? Bu algoritma, linked list'in en temel işlemlerinden biridir. Mülakatlarda sıkça sorulur çünkü pointer manipülasyonunu ne kadar iyi anladığınızı test eder. Ayrıca birçok başka algoritmanın (örneğin palindrom kontrolü) alt problemi olarak kullanılır.

📍 Başlangıç Durumu:

Aşağıda 4 elemanlı bir linked list görüyorsunuz. HEAD işaretçisi listenin başını gösterir - bu bizim listeye erişim noktamız.

HEAD 1 2 3 4 NULL

🔄 Tersine Çevirme Sonrası:

Tüm okların yönü değişti! Artık HEAD son elemana (4) işaret ediyor ve her node önceki node'a bağlı.

NULL 1 2 3 4 HEAD

💡 Algoritmanın Mantığı:

Üç işaretçi kullanıyoruz: prev (önceki), current (şu anki), next_temp (geçici olarak sonraki). Her adımda:

  1. Sonrakini kaydet: next_temp = current.next (yoksa kaybolur!)
  2. Bağlantıyı tersine çevir: current.next = prev
  3. İşaretçileri ilerlet: prev = current, current = next_temp
Python Kodu
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

def reverse_list(head):
    """
    Linked List'i Tersine Çevir - O(n) zaman, O(1) alan
    
    Mantık: 3 pointer kullan (prev, current, next_temp)
    Her adımda ok yönünü tersine çevir.
    """
    prev = None
    current = head
    
    print("🔄 Tersine çevirme başlıyor...\n")
    step = 0
    
    while current:
        step += 1
        next_temp = current.next  # Sonrakini kaydet
        current.next = prev       # Yönü tersine çevir
        
        print(f"Adım {step}: {current.val}.next = {prev.val if prev else 'None'}")
        
        prev = current            # prev ilerle
        current = next_temp       # current ilerle
    
    return prev  # Yeni head

# Test
def create_list(values):
    if not values: return None
    head = Node(values[0])
    current = head
    for val in values[1:]:
        current.next = Node(val)
        current = current.next
    return head

def print_list(head):
    result = []
    while head:
        result.append(str(head.val))
        head = head.next
    print(" → ".join(result) + " → NULL")

# Demo
head = create_list([1, 2, 3, 4, 5])
print("Önce: ", end="")
print_list(head)

new_head = reverse_list(head)
print("\nSonra: ", end="")
print_list(new_head)
Çıktı bekleniyor...
🔍

2. Döngü Tespiti (Floyd's Algorithm)

Medium LeetCode #141

Problem: Bir linked list'te döngü (cycle) olup olmadığını tespit et. Döngü, listenin son node'unun NULL yerine listedeki başka bir node'a işaret etmesi demektir.

Neden zor? Normal bir linked list'te sona kadar gidip NULL görürüz. Ama döngülü bir listede sonsuza kadar döneriz! Peki nasıl anlayacağız?

Çözüm - Tavşan ve Kaplumbağa Algoritması: Bu zarif algoritma, farklı hızlarda hareket eden iki işaretçi kullanır. Eğer döngü varsa, hızlı olan yavaş olanı mutlaka yakalar!

🐢🐰 Floyd's Cycle Detection Animasyonu

🐢 Slow (Kaplumbağa)

Her adımda 1 node ilerler. Yavaş ama istikrarlı!

🐰 Fast (Tavşan)

Her adımda 2 node ilerler. Hızlı koşuyor!

Mantık: Düz bir yolda tavşan hep önde kalır. Ama dairesel bir pistte? Tavşan o kadar hızlı koşar ki sonunda kaplumbağaya arkadan yetişir!

📍 Döngülü Liste Örneği:

HEAD 1 2 döngü başı 3 4 5 ↩ Döngü! 5 → 3'e geri dönüyor

🎯 Adım Adım Takip:

Adım 🐢 Slow 🐰 Fast Buluştular mı?
BaşlangıçNode 1Node 1-
1Node 2Node 3
2Node 3Node 5
3Node 4Node 4 (5→3→4)✅ YAKALADI!

⚡ Neden çalışıyor? Döngü içinde, her adımda tavşan kaplumbağaya 1 node daha yaklaşır. Sonunda mutlaka aynı node'da buluşurlar!

Python Kodu
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

def has_cycle(head):
    """
    Floyd's Cycle Detection - O(n) zaman, O(1) alan
    
    Tavşan-Kaplumbağa algoritması:
    - Slow: 1 adım
    - Fast: 2 adım
    - Eğer buluşurlarsa → DÖNGÜ VAR!
    """
    if not head or not head.next:
        return False
    
    slow = head  # 🐢 Kaplumbağa
    fast = head  # 🐰 Tavşan
    
    step = 0
    while fast and fast.next:
        step += 1
        slow = slow.next        # 1 adım
        fast = fast.next.next   # 2 adım
        
        print(f"Adım {step}: 🐢 slow={slow.val}, 🐰 fast={fast.val if fast else 'None'}")
        
        if slow == fast:
            print(f"\n🎯 BULUŞTULAR! Node {slow.val}'de döngü tespit edildi!")
            return True
    
    print("\n✅ Döngü yok!")
    return False

# Test 1: Döngü yok
print("=== TEST 1: Döngü olmayan liste ===")
head1 = Node(1)
head1.next = Node(2)
head1.next.next = Node(3)
has_cycle(head1)

# Test 2: Döngü var
print("\n=== TEST 2: Döngülü liste ===")
head2 = Node(1)
head2.next = Node(2)
head2.next.next = Node(3)
head2.next.next.next = Node(4)
head2.next.next.next.next = head2.next  # 4 → 2 (döngü!)
has_cycle(head2)
Çıktı bekleniyor...
🎯

3. Orta Noktayı Bul

Easy LeetCode #876

Problem: Linked list'in orta node'unu bul. Ama dikkat: Listeyi sadece bir kez gezmek zorundasın!

Neden tek geçiş önemli? Eğer iki kez gezseydik, önce uzunluğu bulur (n adım), sonra n/2'ye giderdik (n/2 adım). Toplam 1.5n adım. Ama tek geçişle n adımda bitiriyoruz!

Çözüm - Yine İki İşaretçi: Aynı Floyd mantığı! Slow 1 adım, Fast 2 adım. Fast sona ulaştığında, Slow tam ortada olur. Neden? Çünkü Fast, Slow'un iki katı hızda gidiyor. Fast tüm yolu bitirdiğinde, Slow yarı yolu bitirmiş olur!

🎮 İnteraktif Animasyon

Aşağıda rastgele bir linked list oluşturup algoritmanın adım adım nasıl çalıştığını görebilirsiniz.

🎲 "Yeni Liste Oluştur" butonuna tıklayın

Algoritma başlatılmadı...

📊 Örnek Çalışma (7 Elemanlı Liste):

Adım 🐢 Slow Konumu 🐰 Fast Konumu Fast Bitti mi?
Başlangıç1. node1. nodeHayır
12. node3. nodeHayır
23. node5. nodeHayır
34. node ✅7. nodeEvet!

Sonuç: 7 elemanlı listede orta eleman 4. node'dur (indeks 3). Fast sona ulaştığında Slow tam ortada!

❓ Çift Sayıda Eleman Olursa?

6 elemanlı listede iki "orta" vardır (3. ve 4.). Bu algoritma ikinci ortayı (4. node) döndürür. LeetCode sorusu da bunu ister.

Python Kodu
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

def find_middle(head):
    """
    Orta Noktayı Bul - O(n) zaman, O(1) alan
    
    Fast sona ulaştığında, slow ortada!
    """
    if not head:
        return None
    
    slow = head
    fast = head
    
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    
    return slow

# Test
def create_list(values):
    if not values: return None
    head = Node(values[0])
    current = head
    for val in values[1:]:
        current.next = Node(val)
        current = current.next
    return head

# Tek sayıda eleman
print("=== Tek sayı (5 eleman) ===")
head1 = create_list([1, 2, 3, 4, 5])
middle1 = find_middle(head1)
print(f"Liste: 1 → 2 → 3 → 4 → 5")
print(f"Orta: {middle1.val}")

# Çift sayıda eleman
print("\n=== Çift sayı (6 eleman) ===")
head2 = create_list([1, 2, 3, 4, 5, 6])
middle2 = find_middle(head2)
print(f"Liste: 1 → 2 → 3 → 4 → 5 → 6")
print(f"Orta: {middle2.val} (ikinci orta)")
Çıktı bekleniyor...
🔀

4. İki Sıralı Listeyi Birleştir

Easy LeetCode #21

Problem: Elimizde iki tane sıralı (küçükten büyüğe) linked list var. Bunları birleştirerek tek bir sıralı liste oluştur.

Gerçek hayat örneği: İki farklı kaynaktan gelen sıralı veri akışlarını birleştirmek. Örneğin, iki farklı sensörden gelen zamana göre sıralı ölçümleri tek bir kronolojik listeye birleştirmek.

Algoritma mantığı: Her iki listenin başından başla. Hangi listenin başındaki eleman daha küçükse, onu al ve sonuç listesine ekle. Sonra o listede bir adım ilerle. Bu işlemi listelerden biri bitene kadar tekrarla. Kalan elemanları olduğu gibi ekle.

📍 Başlangıç Durumu:

Liste 1: 1 3 5 NULL Liste 2: 2 4 6 NULL Birleşik: 1 2 3 4 5 6 NULL

🔄 Adım Adım Birleştirme:

Adım L1 Başı L2 Başı Karşılaştırma Eklenen
1121 < 21 (L1'den)
2323 > 22 (L2'den)
3343 < 43 (L1'den)
4545 > 44 (L2'den)
5565 < 65 (L1'den)
6-6L1 bitti6 (L2 kalan)

💡 Dummy Node Tekniği:

Kodda dummy = Node(0) görüyorsunuz. Bu "sahte" node neden var?

  • Edge case'leri basitleştirir: Listeye ilk elemanı eklerken özel kontrol yapmamıza gerek kalmaz
  • current.next = ... her zaman çalışır, ilk eleman için bile
  • Sonuçta: dummy.next gerçek listenin başını verir

Bu teknik linked list problemlerinde çok yaygın kullanılır. Öğrenin!

Python Kodu
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

def merge_sorted_lists(l1, l2):
    """
    İki Sıralı Listeyi Birleştir - O(n+m) zaman, O(1) alan
    """
    # Dummy head (işleri kolaylaştırır)
    dummy = Node(0)
    current = dummy
    
    while l1 and l2:
        if l1.val <= l2.val:
            current.next = l1
            l1 = l1.next
        else:
            current.next = l2
            l2 = l2.next
        current = current.next
    
    # Kalan elemanları ekle
    current.next = l1 if l1 else l2
    
    return dummy.next

# Test
def create_list(values):
    if not values: return None
    head = Node(values[0])
    current = head
    for val in values[1:]:
        current.next = Node(val)
        current = current.next
    return head

def print_list(head):
    result = []
    while head:
        result.append(str(head.val))
        head = head.next
    return " → ".join(result)

# Demo
l1 = create_list([1, 3, 5, 7])
l2 = create_list([2, 4, 6, 8])

print(f"Liste 1: {print_list(l1)}")
print(f"Liste 2: {print_list(l2)}")

merged = merge_sorted_lists(l1, l2)
print(f"\n🔀 Birleşik: {print_list(merged)}")
Çıktı bekleniyor...

📚 Daha Fazla Algoritma

Problem Zorluk Anahtar Teknik
Remove Nth Node from End Medium İki pointer (n fark)
Palindrome Linked List Easy Orta bul + Yarısını ters çevir
Intersection of Two Lists Easy İki pointer (uzunluk eşitle)
LRU Cache Hard Doubly LL + HashMap