0Pricing
Coding Interview Prep · Ders

İki İşaretçi: Yavaş ve Hızlı

Yinelenenleri yerinde kaldırmak, sıfırları taşımak ve dizileri bir dönüm noktası değerine göre bölümlendirmek için yavaş-hızlı işaretçi kalıbını uygulayın.

İki İşaretçi: Yavaş ve Hızlı, CoddyKit'te ücretsiz bir Coding 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, Coding Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Coding Interview Prep kursu toplamda 4 dersten oluşur.

Yavaş ve Hızlı İşaretçileri Anlamak

Yavaş-hızlı işaretçi örüntüsü (kaplumbağa ve tavşan olarak da adlandırılır), aynı dizi içinde farklı hızlarda ilerleyen iki işaretçi kullanır. Zıt uçlu işaretçilerin aksine, ikisi de başlangıçta başlar. Yavaş işaretçi her seferinde bir adım, hızlı işaretçi ise iki (veya daha fazla) adım ilerler. Hız farkları yararlı değişmezler oluşturur: yavaş işaretçi “geçerli öneki” izlerken hızlı işaretçi koşulları bulmak için ileriyi tarar.

# Slow pointer marks the write position;
# Fast pointer scans for next non-duplicate.

def remove_duplicates(nums):
    if not nums: return 0
    slow = 0  # next position to write a unique value
    for fast in range(1, len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1  # new length

nums = [1, 1, 2, 3, 3, 3, 4]
k = remove_duplicates(nums)
print(nums[:k])  # [1, 2, 3, 4]

Sıralı Diziden Tekrarları Kaldırma

Sıralı bir dizide tekrarlar bitişiktir. Yavaş işaretçi yazılan son benzersiz değeri izler; hızlı işaretçi ileriyi tarar. Hızlı işaretçi nums[slow] değerinden farklı bir değere ulaştığında yavaşı ilerletin ve yeni değeri kopyalayın. Bu yerinde çalışan algoritma, O(1) ek alan kullanarak O(n) zamanda çalışır; okuma-yazma işaretçisi örüntüsündeki yetkinliğinizi ölçen standart bir mülakat sorusudur.

def remove_duplicates_v2(nums):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != nums[slow]:
            slow += 1
            nums[slow] = nums[fast]
    return slow + 1

# Allow at most 2 occurrences
def remove_duplicates_k2(nums):
    slow = 0
    for fast in range(len(nums)):
        if slow < 2 or nums[fast] != nums[slow - 2]:
            nums[slow] = nums[fast]
            slow += 1
    return slow

print(remove_duplicates_k2([1,1,1,2,2,3]))
# Result: 5, nums[:5] = [1,1,2,2,3]

Yavaş-Hızlı İşaretçilerle Sıfırları Taşıma

Sıfır olmayan öğelerin göreli sırasını koruyarak tüm sıfırları sona taşıyın. Yavaş işaretçi, sıfır olmayan bir öğenin yazılacağı bir sonraki konumu gösterir. Hızlı işaretçi sıfır olmayan değerleri tarar. Hızlı işaretçi böyle bir değer bulduğunda onu yavaş işaretçinin konumuna kopyalayın ve her iki işaretçiyi de ilerletin. Tarama bittikten sonra yavaş işaretçinin bulunduğu konumdan sona kadar olan konumları sıfırlarla doldurun. Zaman karmaşıklığı O(n), alan karmaşıklığı O(1)'dir.

def move_zeroes(nums):
    slow = 0  # next position for a non-zero
    for fast in range(len(nums)):
        if nums[fast] != 0:
            nums[slow] = nums[fast]
            slow += 1
    # Fill rest with zeroes
    while slow < len(nums):
        nums[slow] = 0
        slow += 1

nums = [0, 1, 0, 3, 12]
move_zeroes(nums)
print(nums)  # [1, 3, 12, 0, 0]

Diziyi Pivot Çevresinde Bölümlendirme

Hızlı sıralamanın bölümlendirme alt adımı, tüm < pivot değerlerinin >= pivot değerlerinden önce gelmesi için öğeleri yerinde yeniden düzenler. Lomuto düzeni, küçük bir öğenin son konumunu gösteren yavaş bir işaretçi ile ileri doğru tarama yapan hızlı bir işaretçi kullanır. Hızlı işaretçi küçük bir öğe bulduğunda yavaşı artırın ve öğeleri yer değiştirin. Bu işlem O(1) ek alan kullanarak O(n) zamanda çalışır.

def lomuto_partition(nums, low, high):
    pivot = nums[high]
    slow = low - 1  # last position of small element
    for fast in range(low, high):
        if nums[fast] <= pivot:
            slow += 1
            nums[slow], nums[fast] = nums[fast], nums[slow]
    # Place pivot in final position
    nums[slow+1], nums[high] = nums[high], nums[slow+1]
    return slow + 1  # pivot's final index

arr = [3, 1, 4, 1, 5, 9, 2, 6]
p = lomuto_partition(arr, 0, len(arr)-1)
print(arr)   # elements before p are <= pivot

Bağlı Listenin Ortasını Bulma

Bağlı bir listede yavaş-hızlı işaretçiler kullanıldığında hızlı işaretçi her adımda iki düğüm, yavaş işaretçi ise bir düğüm ilerler. Hızlı işaretçi sona ulaştığında yavaş işaretçi ortadadır. Bu O(n) zamanlı tek geçişli yaklaşım, önce düğümleri sayıp sonra listenin yarısı boyunca ilerlemekten çok daha temizdir. Bağlı listeler için birleştirmeli sıralamada ve palindrom bağlı liste algılamasında alt adım olarak kullanılır.

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

def find_middle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow  # slow is at middle

# Build 1->2->3->4->5
h = Node(1, Node(2, Node(3, Node(4, Node(5)))))
mid = find_middle(h)
print(mid.val)  # 3  (middle of 5 nodes)

Döngü Algılama: Floyd'un Kaplumbağası ve Tavşanı

Floyd'un döngü algılama algoritması, yavaş ve hızlı işaretçileri bağlı listenin başına yerleştirir. Yavaş işaretçi bir düğüm, hızlı işaretçi iki düğüm ilerler. Bir döngü varsa hızlı işaretçi sonunda yavaşa tur bindirir ve ikisi döngünün içinde buluşur. Hızlı işaretçi sona ulaşırsa döngü yoktur. Buluşma garanti edilir; çünkü hızlı işaretçi her yinelemede yavaştan bir adım daha fazla ilerler. Uzunluğu k olan bir döngüde, yavaş işaretçinin döngüye girmesinden sonraki k adım içinde buluşurlar.

class ListNode:
    def __init__(self, val=0, nxt=None):
        self.val = val
        self.next = nxt

def has_cycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:  # identity check (same object)
            return True
    return False

# 1->2->3->4->2 (cycle at node 2)
n1 = ListNode(1)
n2 = ListNode(2)
n3 = ListNode(3)
n4 = ListNode(4)
n1.next=n2; n2.next=n3; n3.next=n4; n4.next=n2
print(has_cycle(n1))  # True

Döngü Giriş Noktasını Bulma

Bir döngüyü algıladıktan sonra (slow == fast), işaretçilerden birini başa getirin. Şimdi her iki işaretçiyi de her seferinde bir adım ilerletin. Döngünün giriş noktasında buluşacaklardır. Bu yaklaşım, baştan döngü girişine olan uzaklığın buluşma noktasından döngü girişine olan uzaklığa, döngü uzunluğuna göre kalan alınarak, eşit olması matematiksel özelliğini kullanır. Bu, zor mülakat sorularında sıkça karşılaşılan güzel bir matematiksel sonuçtur.

def detect_cycle(head):
    slow = fast = head
    # Phase 1: detect
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            break
    else:
        return None  # no cycle
    # Phase 2: find entry
    slow = head
    while slow is not fast:
        slow = slow.next
        fast = fast.next
    return slow  # cycle entry node

# Using same cycled list as previous scene
print(detect_cycle(n1).val)  # 2  (cycle entry)

Mutlu Sayı için Yavaş-Hızlı İşaretçiler

Yavaş-hızlı işaretçiler, bağlı listelerin ötesinde döngü oluşturan her işleme uygulanabilir. Bir “mutlu sayı”, rakam kareleri toplamları arasında döngü oluşturur; n mutlu değilse dizi sonunda bir döngüye girer. Döngüyü, bir adımda bir rakam karesi ilerleyen yavaş işaretçi ve iki adım ilerleyen hızlı işaretçiyle algılayın. İkisi 1'de buluşursa n mutludur; aksi hâlde 1 olmayan bir döngüde sıkışmıştır. Bu, Floyd algoritmasının sanal değerler bağlı listesine uygulanmış hâlidir.

def is_happy(n):
    def next_val(x):
        total = 0
        while x:
            x, d = divmod(x, 10)
            total += d * d
        return total

    slow = n
    fast = next_val(n)
    while fast != 1 and slow != fast:
        slow = next_val(slow)
        fast = next_val(next_val(fast))
    return fast == 1

print(is_happy(19))   # True  (1->9->...->1)
print(is_happy(2))    # False (enters a cycle)

Listenin Sonundan N'inci Düğüm

İki işaretçi kullanarak bağlı listenin sonundan n'inci düğümü tek geçişte bulun. Hızlı işaretçiyi n adım öne ilerletin. Ardından hızlı işaretçi sona ulaşana kadar her iki işaretçiyi birlikte ilerletin; yavaş işaretçi artık sondan n'inci düğümdedir. Bu düğümü silmek için yavaş işaretçinin bir adım gerisinde duran bir “önceki” işaretçisini tutun. Bu, toplam uzunluğu önce sayma gereğini ortadan kaldıran klasik bir tek geçişli bağlı liste problemidir.

def remove_nth_from_end(head, n):
    dummy = ListNode(0)
    dummy.next = head
    fast = slow = dummy
    # Advance fast n+1 steps
    for _ in range(n + 1):
        fast = fast.next
    # Advance together
    while fast:
        slow = slow.next
        fast = fast.next
    # slow.next is the nth from end
    slow.next = slow.next.next
    return dummy.next

# Build 1->2->3->4->5, remove 2nd from end
h2 = ListNode(1,ListNode(2,ListNode(3,ListNode(4,ListNode(5)))))
result = remove_nth_from_end(h2, 2)
# Should give 1->2->3->5

Dize Sorularında Yavaş-Hızlı İşaretçiler

Yavaş-hızlı düşünme, dizi ve dize problemlerine de uygulanabilir. Çalışma uzunluğu kodlamalı bir dizeyi sıkıştırırken yavaş işaretçi yazma konumunu, hızlı işaretçi ise her çalışmanın sonuna kadar olan kısmı tarar. Çalışmadaki tüm karakterler yavaş işaretçinin karakterine eşitse hızlı işaretçiyi ilerletin; değilse çalışmayı kaydedip yavaş işaretçiyi güncelleyin. Bu yaklaşım, tek geçişte O(n) zaman ve O(1) alan sağlar.

def compress(chars):
    slow = fast = 0
    while fast < len(chars):
        char = chars[fast]
        count = 0
        # Count the run
        while fast < len(chars) and chars[fast] == char:
            fast += 1
            count += 1
        chars[slow] = char
        slow += 1
        if count > 1:
            for c in str(count):
                chars[slow] = c
                slow += 1
    return slow

chars = list('aabcccccaa')
print(compress(chars))  # 6
print(chars[:6])        # ['a','2','b','c','5','a']... wait
# Actually: ['a','2','b','c','5','a','2']

Yavaş-Hızlı ve Zıt Uçlu İşaretçiler Arasında Seçim

Zıt uçlu işaretçileri, problem hedef toplamı veren çiftleri, palindrom denetimlerini veya her iki taraftan daraltılan bir pencereyi içerdiğinde kullanın. Yavaş-hızlı işaretçileri, bir yazma işaretçisine ihtiyaç duyduğunuzda (öğeleri kaldırma/taşıma), bağlı liste yapısını işlerken (orta nokta, döngü) veya herhangi bir değer dizisindeki döngüleri algılarken kullanın. Her iki yaklaşım da iç içe döngüleri ortadan kaldırır ve O(n) zaman sağlar; belirleyici etken, gezinmenin yapısıdır.

# Pattern matcher:
# 1. Sorted array, target sum -> OPPOSITE ENDS
# 2. Remove/filter elements in-place -> SLOW-FAST (read-write)
# 3. Linked list middle/cycle -> SLOW-FAST (1x vs 2x speed)
# 4. Detect cycle in value sequence -> SLOW-FAST (Floyd)

# Example: given sorted array, remove val in-place
def remove_sorted(nums, val):
    slow = 0
    for fast in range(len(nums)):
        if nums[fast] != val:
            nums[slow] = nums[fast]
            slow += 1
    return slow

nums = [0,1,2,2,3,0,4,2]
print(remove_sorted(nums, 2))  # 5

Hızlı Kontrol

Bu derste öğrendiğiniz Veri Yapıları & Algoritmalar — Kodlama Mülakatına Hazırlık kavramlarını anlayıp anlamadığınızı sınayın.

Ders Özeti

Bu derste şunları öğrendiniz: yavaş-hızlı (okuma-yazma) örüntüsü, bir hızlı işaretçi ileri doğru tarama yaparken yazma işaretçisini bir sonraki geçerli konumda tutar; yerinde kaldırma, tekrarları kaldırma ve sıfırları taşımanın temelini oluşturur, Floyd'un kaplumbağa ve tavşan algoritması, iki işaretçi arasındaki hız farkından yararlanarak döngüleri O(n) zamanda ve O(1) alan kullanarak algılar ve bir döngü algılandıktan sonra bir işaretçiyi başa getirip ikisini aynı hızda ilerletmek, kanıtlanabilir uzaklık eşitliği sayesinde döngünün girişini bulur. Sırada mülakatlar için Python dize API'sini inceleyeceğiz.

Sıkça Sorulan Sorular

“İki İşaretçi: Yavaş ve Hızlı” dersi ücretsiz mi?

Evet — “İki İşaretçi: Yavaş ve Hızlı” 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 Coding Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Coding Interview Prep kursu toplamda 4 dersten oluşur.

“İki İşaretçi: Yavaş ve Hızlı” dersinde ne öğreneceğim?

Yinelenenleri yerinde kaldırmak, sıfırları taşımak ve dizileri bir dönüm noktası değerine göre bölümlendirmek için yavaş-hızlı işaretçi kalıbını uygulayın. Coding 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.

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

Önceden deneyim gerekmez. CoddyKit'te Coding 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.

“İki İşaretçi: Yavaş ve Hızlı” 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 Coding Interview Prep dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Coding 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. Dizi Temelleri ve Yerinde İşlemler
  2. Ön Ek Toplamları ve Birikimli Toplamlar
  3. İki İşaretçi: Karşıt Uçlar
  4. İki İşaretçi: Yavaş ve Hızlı
← Coding Interview Prep Sayfasına Dön