DSA Interview Prep · Les

Twee pointers: langzaam en snel

Pas het slow-fast-pointerpatroon toe om duplicaten in-place te verwijderen, nullen te verplaatsen en arrays rond een pivotwaarde te partitioneren.

Les 4 van 413 stappen

Twee pointers: langzaam en snel is een gratis DSA Interview Prep-les op CoddyKit. Dit is les 4 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject DSA Interview Prep. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus DSA Interview Prep bevat in totaal 4 lessen.

Trage en snelle pointers uitgelegd

Het patroon met trage en snelle pointers (ook wel de schildpad-en-haas genoemd) gebruikt twee pointers die met verschillende snelheden door dezelfde reeks bewegen. In tegenstelling tot pointers aan tegenovergestelde uiteinden beginnen ze allebei aan het begin. De trage pointer gaat telkens één stap vooruit; de snelle pointer gaat twee of meer stappen vooruit. Hun snelheidsverschil zorgt voor nuttige invarianten: de trage pointer houdt een 'geldig voorvoegsel' bij, terwijl de snelle pointer vooruitkijkt naar voorwaarden.

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

Duplicaten uit een gesorteerde array verwijderen

In een gesorteerde array staan duplicaten naast elkaar. De trage pointer houdt de laatst weggeschreven unieke waarde bij; de snelle pointer kijkt vooruit. Zodra de snelle pointer een waarde bereikt die verschilt van nums[slow], schuif je slow op en kopieer je de nieuwe waarde. Dit algoritme ter plekke werkt in O(n) tijd met O(1) extra ruimte — een standaardvraag in technische sollicitatiegesprekken die je beheersing van het lees-schrijfpatroon met pointers toetst.

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]

Nullen verplaatsen met trage en snelle pointers

Verplaats alle nullen naar het einde en behoud daarbij de onderlinge volgorde van de niet-nulelementen. De trage pointer markeert de volgende positie voor een niet-nulelement. De snelle pointer zoekt naar niet-nulwaarden. Zodra fast er een vindt, kopieer je die naar de positie van slow en schuif je beide pointers op. Vul na het doorlopen de posities vanaf slow tot het einde met nullen. O(n) tijd, O(1) ruimte.

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]

Een array rond een spilwaarde partitioneren

De partitioneringsstap van quicksort herschikt elementen ter plekke, zodat alle waarden < spilwaarde vóór waarden >= spilwaarde komen. Het schema van Lomuto gebruikt een trage pointer, die de laatste positie van een klein element markeert, en een snelle pointer, die vooruit scant. Wanneer fast een klein element vindt, verhoog je slow en wissel je de elementen om. Dit werkt in O(n) tijd met O(1) extra ruimte.

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

Het midden van een gekoppelde lijst vinden

Met trage en snelle pointers in een gekoppelde lijst gaat de snelle pointer per stap twee knopen vooruit en de trage pointer één. Wanneer fast het einde bereikt, staat slow in het midden. Deze aanpak in één doorgang met O(n) is veel eenvoudiger dan eerst de knopen tellen en daarna tot halverwege lopen. Je gebruikt deze aanpak als deelstap in mergesort voor gekoppelde lijsten en bij het detecteren van palindromen in gekoppelde lijsten.

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)

Cycli detecteren: Floyds schildpad en haas

Bij Floyds cyclusdetectie plaats je de trage en snelle pointers aan het begin van een gekoppelde lijst. Slow gaat één knoop vooruit; fast gaat er twee vooruit. Als er een cyclus bestaat, zal de snelle pointer de trage pointer uiteindelijk inhalen en ontmoeten ze elkaar binnen de cyclus. Als fast None bereikt, is er geen cyclus. De ontmoeting is gegarandeerd omdat fast bij elke iteratie één stap op slow wint — in een cyclus met lengte k ontmoeten ze elkaar binnen k stappen nadat slow de cyclus binnengaat.

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

Het beginpunt van een cyclus vinden

Nadat je een cyclus hebt gedetecteerd (slow == fast), zet je één pointer terug naar het begin. Schuif nu beide pointers telkens één stap vooruit. Ze ontmoeten elkaar bij het beginpunt van de cyclus. Dit maakt gebruik van de wiskundige eigenschap dat de afstand van het begin tot het beginpunt van de cyclus gelijk is aan de afstand van het ontmoetingspunt tot het beginpunt van de cyclus, modulo de cycluslengte. Dit is een mooi wiskundig resultaat dat vaak voorkomt in lastige opgaven voor technische sollicitatiegesprekken.

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)

Trage en snelle pointers voor geluksgetallen

Trage en snelle pointers zijn niet beperkt tot gekoppelde lijsten, maar werken voor elk proces met een cyclus. Een 'geluksgetal' doorloopt cyclisch sommen van kwadraten van cijfers — als n geen geluksgetal is, komt de reeks uiteindelijk in een lus terecht. Detecteer de lus met slow (één stap = één som van cijferkwadraten) en fast (twee stappen). Als ze elkaar bij 1 ontmoeten, is n een geluksgetal; anders zit n vast in een cyclus die niet bij 1 uitkomt. Dit is Floyds algoritme toegepast op een virtuele gekoppelde lijst van waarden.

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)

Het n-de element vanaf het einde van een lijst

Vind het n-de element vanaf het einde van een gekoppelde lijst in één doorgang met twee pointers. Schuif de snelle pointer n stappen vooruit. Schuif daarna beide pointers samen op totdat fast het einde bereikt — slow staat nu bij het n-de element vanaf het einde. Houd een pointer met de naam 'prev' één stap achter slow om dit element te verwijderen. Dit is een klassiek probleem met gekoppelde lijsten in één doorgang, waarbij je niet eerst de totale lengte hoeft te tellen.

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

Trage en snelle pointers bij stringproblemen

Het denken in trage en snelle pointers is ook toepasbaar op array- en stringproblemen. Bij het comprimeren van een run-length-gecodeerde string markeert de trage pointer de schrijfpositie en loopt de snelle pointer naar het einde van elke reeks. Als alle tekens in de reeks gelijk zijn aan het teken van slow, schuif je fast op; anders leg je de reeks vast en werk je slow bij. Dit bereikt O(n) in één doorgang met O(1) ruimte.

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

Kiezen tussen trage en snelle pointers en pointers aan tegenovergestelde uiteinden

Gebruik pointers aan tegenovergestelde uiteinden wanneer het probleem gaat over paren met een bepaalde som, palindroomcontroles of het van beide kanten verkleinen van een venster. Gebruik trage en snelle pointers wanneer je een schrijfpositie nodig hebt (elementen verwijderen of verplaatsen), wanneer je de structuur van een gekoppelde lijst verwerkt (midden, cyclus) of wanneer je cycli in een willekeurige waardenreeks detecteert. Beide elimineren geneste lussen en bereiken O(n) — de doorslaggevende factor is de structuur van het doorlopen.

# 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

Korte controle

Test je begrip van de concepten uit Data Structures & Algorithms — Coding Interview Prep in deze les.

Lesoverzicht

In deze les heb je geleerd dat het trage-en-snelle-patroon (lees-schrijven) een schrijfpositie bij de volgende geldige positie houdt terwijl een snelle pointer vooruit scant — de basis voor ter plekke verwijderen, dedupliceren en nullen verplaatsen, dat Floyds schildpad-en-haasalgoritme cycli detecteert in O(n) tijd en O(1) ruimte door het snelheidsverschil tussen twee pointers te benutten en dat je na het detecteren van een cyclus één pointer terug naar het begin kunt zetten en beide pointers met dezelfde snelheid kunt laten bewegen om het begin van de cyclus te vinden, dankzij een aantoonbare gelijkheid van afstanden. Hierna bekijken we de Python-string-API voor technische sollicitatiegesprekken.

Gratis beginnen

Leer Python met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
30
Lessen
120

Veelgestelde vragen

Is de les “Twee pointers: langzaam en snel” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad DSA Interview Prep, waaronder “Twee pointers: langzaam en snel”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus DSA Interview Prep bevat in totaal 4 lessen.

Wat leer ik in “Twee pointers: langzaam en snel”?

Pas het slow-fast-pointerpatroon toe om duplicaten in-place te verwijderen, nullen te verplaatsen en arrays rond een pivotwaarde te partitioneren. Je oefent met DSA Interview Prep door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met DSA Interview Prep te beginnen?

Ervaring vooraf is niet nodig. DSA Interview Prep op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 4 van 4.

Hoe lang duurt de les “Twee pointers: langzaam en snel”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over DSA Interview Prep?

Ja. Elke les over DSA Interview Prep bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Basisprincipes van arrays en in-place-bewerkingen
  2. Prefixsommen en lopende totalen
  3. Twee pointers: tegenovergestelde uiteinden
  4. Twee pointers: langzaam en snel
← Terug naar DSA Interview Prep