Klasik Mülakat Soruları ve Çözümleri
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.
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.
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ı.
Üç işaretçi kullanıyoruz: prev (önceki), current (şu anki), next_temp (geçici olarak sonraki). Her adımda:
next_temp = current.next (yoksa kaybolur!)current.next = prevprev = current, current = next_tempclass 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)
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!
Her adımda 1 node ilerler. Yavaş ama istikrarlı!
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!
| Adım | 🐢 Slow | 🐰 Fast | Buluştular mı? |
|---|---|---|---|
| Başlangıç | Node 1 | Node 1 | - |
| 1 | Node 2 | Node 3 | ❌ |
| 2 | Node 3 | Node 5 | ❌ |
| 3 | Node 4 | Node 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!
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)
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!
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
| Adım | 🐢 Slow Konumu | 🐰 Fast Konumu | Fast Bitti mi? |
|---|---|---|---|
| Başlangıç | 1. node | 1. node | Hayır |
| 1 | 2. node | 3. node | Hayır |
| 2 | 3. node | 5. node | Hayır |
| 3 | 4. node ✅ | 7. node | Evet! |
Sonuç: 7 elemanlı listede orta eleman 4. node'dur (indeks 3). Fast sona ulaştığında Slow tam ortada!
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.
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)")
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.
| Adım | L1 Başı | L2 Başı | Karşılaştırma | Eklenen |
|---|---|---|---|---|
| 1 | 1 | 2 | 1 < 2 | 1 (L1'den) |
| 2 | 3 | 2 | 3 > 2 | 2 (L2'den) |
| 3 | 3 | 4 | 3 < 4 | 3 (L1'den) |
| 4 | 5 | 4 | 5 > 4 | 4 (L2'den) |
| 5 | 5 | 6 | 5 < 6 | 5 (L1'den) |
| 6 | - | 6 | L1 bitti | 6 (L2 kalan) |
Kodda dummy = Node(0) görüyorsunuz. Bu "sahte" node neden var?
dummy.next gerçek listenin başını verirBu teknik linked list problemlerinde çok yaygın kullanılır. Öğrenin!
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)}")