Voorbereiding op programmeerinterviews · Les

Lussen en geneste lussen analyseren

Bereken de tijdcomplexiteit van enkele lussen, geneste lussen en lussen met krimpende bereiken, zoals bij binary search of driehoeksiteraties.

Les 2 van 413 stappen

Lussen en geneste lussen analyseren is een gratis Voorbereiding op programmeerinterviews-les op CoddyKit. Dit is les 2 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Voorbereiding op programmeerinterviews. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.

Eén lus: O(n)

De eenvoudigste lus voert de inhoud n keer uit en is dus O(n). Een grotere stap verandert het aantal uitvoeringen, maar niet de klasse. Begin altijd met tellen hoe vaak de inhoud wordt uitgevoerd. Bekijk de code.

# O(n): body runs n times
def count_ops_linear(n):
    ops = 0
    for i in range(n):
        ops += 1     # constant work
    return ops

print(count_ops_linear(100))  # 100

# Still O(n): step=2 halves count but same class
def count_ops_half(n):
    ops = 0
    for i in range(0, n, 2):
        ops += 1
    return ops

print(count_ops_half(100))    # 50  => O(n)

Geneste lussen: O(n²) en verder

Twee geneste lussen die elk n keer lopen, geven n x n = O(n^2); drie lussen geven O(n^3). Maar als de binnenste lus een vast aantal keer loopt, blijft het geheel lineair.

def count_pairs(n):
    ops = 0
    for i in range(n):          # n iterations
        for j in range(n):      # n iterations each
            ops += 1
    return ops

print(count_pairs(10))   # 100 = 10^2
print(count_pairs(100))  # 10000 = 100^2
# Doubling n quadruples ops: classic O(n^2)

Driehoekslus: O(n²/2) = O(n²)

Wanneer de binnenste lus bij i+1 begint, vormen de iteraties een driehoek: n(n-1)/2, wat na het weglaten van de helft nog steeds O(n^2) is. Problemen met alle unieke paren zien er zo uit.

def count_unique_pairs(n):
    ops = 0
    for i in range(n):          # n iterations
        for j in range(i+1, n): # n-1, n-2, ..., 0
            ops += 1
    return ops

print(count_unique_pairs(10))  # 45 = 10*9/2
print(count_unique_pairs(100)) # 4950
# Still O(n^2) -- constant factor 1/2 dropped

Lus met krimpende bereik: O(log n)

Wanneer de lusvariabele bij elke stap wordt gehalveerd, krijg je O(log n). De belangrijkste vraag is: krimpt het bereik vermenigvuldigend (log n) of optellend (n)? Bekijk de code.

def count_log_ops(n):
    ops = 0
    i = n
    while i >= 1:
        ops += 1
        i //= 2   # halve each iteration
    return ops

import math
for n in [8, 16, 64, 1024]:
    ops = count_log_ops(n)
    print(f'n={n}, ops={ops}, log2={int(math.log2(n))}')
# ops tracks log2(n) closely

Geneste lus met krimpende binnenste lus: O(n log n)

Een buitenste lus die n keer loopt met een binnenste lus van O(log n) geeft O(n log n) — de vorm van mergesort. Een binnenste stap van O(log n) herkennen is de sleutel tot het analyseren van sorteringen.

import math

def count_n_log_n(n):
    ops = 0
    for i in range(n):    # n iterations
        j = n
        while j >= 1:     # log n iterations
            ops += 1
            j //= 2
    return ops

for n in [8, 32, 128]:
    ops = count_n_log_n(n)
    predicted = int(n * math.log2(n))
    print(f'n={n}: actual={ops}, n*log2(n)~={predicted}')

Afhankelijke binnenste lussen

Wanneer het bereik van de binnenste lus afhangt van de index van de buitenste lus, tel je het totale aantal iteraties, niet het aantal per stap. Een binnenste lus die van 0 tot i loopt, geeft n(n-1)/2 = O(n^2). Bekijk de code.

# Inner loop runs i times: total = 0+1+2+...+(n-1) = n(n-1)/2 => O(n^2)
def sum_inner_i(n):
    ops = 0
    for i in range(n):
        for j in range(i):   # runs 0,1,2,...,n-1 times
            ops += 1
    return ops

print(sum_inner_i(10))  # 45 = 10*9/2  => O(n^2)

# Inner loop runs n/i times (i doubles): sum ≈ n*log n => O(n log n)
def sum_inner_n_over_i(n):
    ops = 0
    i = 1
    while i <= n:
        for j in range(n // i):
            ops += 1
        i *= 2
    return ops
print(sum_inner_n_over_i(64))  # ~ 64*6 = 384

Bubblesort stap voor stap analyseren

Bubblesort voert n(n-1)/2 vergelijkingen uit en is dus O(n^2). Zelfs met vroegtijdig stoppen heeft een omgekeerd gesorteerde invoer elke vergelijking nodig. Te langzaam voor grote invoeren.

def bubble_sort(arr):
    n = len(arr)
    comparisons = 0
    for i in range(n):
        swapped = False
        for j in range(0, n - i - 1):
            comparisons += 1
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
                swapped = True
        if not swapped:  # early exit if sorted
            break
    return comparisons

arr = list(range(10, 0, -1))  # worst case: reversed
ops = bubble_sort(arr)
print(f'Sorted: {arr}')
print(f'Comparisons: {ops}')  # 45 = 10*9/2

Lussen over strings en substrings

Let op: Python slicing is O(k) en niet gratis, en strings samenvoegen met + in een lus is O(n^2), omdat er elke keer wordt gekopieerd. Gebruik in plaats daarvan ''.join(parts)'. Bekijk de code.

# O(n^2): string concat in loop
def build_bad(n):
    s = ''
    for i in range(n):
        s += str(i)  # copies s each time!
    return s

# O(n): join is a single pass
def build_good(n):
    parts = []
    for i in range(n):
        parts.append(str(i))
    return ''.join(parts)

print(build_good(10))  # '0123456789'

Meerdere invoerparameters

Bij twee invoeren kan de complexiteit beide gebruiken: O(m + n) voor afzonderlijk werk en O(m x n) voor genest werk. Grafen worden vaak beschreven als O(V + E). Geef elke variabele een duidelijke naam.

# O(m + n): two independent loops
def independent(m, n):
    a = sum(range(m))  # O(m)
    b = sum(range(n))  # O(n)
    return a + b       # total O(m + n)

# O(m * n): nested
def nested(m, n):
    count = 0
    for i in range(m):     # O(m)
        for j in range(n): # O(n) each
            count += 1
    return count  # O(m * n)

print(independent(5, 10))  # 10 + 45 = 55
print(nested(5, 10))       # 50

Lussen in lussen versus opeenvolgende aanroepen

Een functieaanroep is niet gratis — de binnenste lus telt ook mee. Roep je een hulpfunctie van O(n) n keer aan, dan krijg je O(n^2). Kijk bij een analyse altijd in aanroepen die als zwarte doos worden behandeld.

# Naive string matching: O(n*m)
def naive_search(text, pattern):
    n, m = len(text), len(pattern)
    matches = []
    for i in range(n - m + 1):  # O(n)
        if text[i:i+m] == pattern:  # O(m) comparison + O(m) slice
            matches.append(i)
    return matches
# Total: O(n*m)

print(naive_search('abcabcabc', 'abc'))  # [0, 3, 6]

Praktijk: complexiteit in één oogopslag herkennen

Maak er een gewoonte van: tel de niveaus van geneste lussen, controleer of de binnenste lus afhangt van de buitenste en let op verborgen kosten in functieaanroepen en slicing. De code is een puzzel om uit te proberen.

# What is the complexity of this function?
def mystery(nums):
    result = []
    for i in range(len(nums)):          # O(n)
        for j in range(i, len(nums)):   # O(n) worst
            if sum(nums[i:j+1]) == 0:   # O(n) slice + sum!
                result.append((i, j))
    return result
# Answer: O(n^3)  -- three nested n-proportional ops
# Outer O(n) x inner O(n) x sum/slice O(n) = O(n^3)

Korte controle

Korte controle — kijk hoe goed de technieken voor lusanalyse zijn blijven hangen. Vertrouw hier op je redenering. 💪

Samenvatting van de les

Samenvatting: geneste lussen vermenigvuldigen en onafhankelijke lussen tellen op, een binnenste lus die halveert geeft O(n log n), en ook verborgen kosten in aanroepen en slicing moeten worden meegeteld.

Gratis beginnen

Leer Voorbereiding op programmeerinterviews 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
90
Lessen
360

Veelgestelde vragen

Is de les “Lussen en geneste lussen analyseren” gratis?

Ja — de volledige tekst van “Lussen en geneste lussen analyseren” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Voorbereiding op programmeerinterviews wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Voorbereiding op programmeerinterviews bevat in totaal 4 lessen.

Wat leer ik in “Lussen en geneste lussen analyseren”?

Bereken de tijdcomplexiteit van enkele lussen, geneste lussen en lussen met krimpende bereiken, zoals bij binary search of driehoeksiteraties. Je oefent met Voorbereiding op programmeerinterviews 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 Voorbereiding op programmeerinterviews te beginnen?

Ervaring vooraf is niet nodig. Voorbereiding op programmeerinterviews 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 2 van 4.

Hoe lang duurt de les “Lussen en geneste lussen analyseren”?

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 Voorbereiding op programmeerinterviews?

Ja. Elke les over Voorbereiding op programmeerinterviews 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. Big-O-notatie vanaf de basis
  2. Lussen en geneste lussen analyseren
  3. Recursie en de recursieboom-methode
  4. Ruimtecomplexiteit en afwegingen
← Terug naar Voorbereiding op programmeerinterviews