İ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 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.
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 <= pivotBağ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)) # TrueDö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->5Dize 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)) # 5Hı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 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.
“İ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. 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.
“İ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 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
- Dizi Temelleri ve Yerinde İşlemler
- Ön Ek Toplamları ve Birikimli Toplamlar
- İki İşaretçi: Karşıt Uçlar
- İki İşaretçi: Yavaş ve Hızlı