Voorbereiding op programmeerinterviews · Les

Meerderheidselement: Boyer-Moore-stemmen

Vind het element dat meer dan n/2 keer voorkomt met het Boyer-Moore-stemalgoritme in lineaire tijd en met O(1)-ruimte, en bewijs de correctheid ervan.

Les 3 van 413 stappen

Meerderheidselement: Boyer-Moore-stemmen is een gratis Voorbereiding op programmeerinterviews-les op CoddyKit. Dit is les 3 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.

Het probleem van het meerderheidselement

Meerderheidselement (LeetCode 169): vind het element dat meer dan n/2 keer voorkomt in een array met lengte n. Het meerderheidselement bestaat altijd dankzij de garantie van het probleem. Voor [3, 2, 3] is het antwoord 3. Voor [2, 2, 1, 1, 1, 2, 2] is het antwoord 2 (het komt 4 van de 7 keer voor). De aanpakken lopen uiteen van sorteren in O(n log n) tot het elegante Boyer-Moore-stemalgoritme in O(n)-tijd en O(1)-ruimte.

# The majority element appears MORE than n/2 times
# So it appears more than all other elements COMBINED

examples = [
    [3, 2, 3],          # 3 appears 2/3 times > 1/2
    [2, 2, 1, 1, 1, 2, 2],  # 2 appears 4/7 times > 3.5
    [1],                # trivially 1
    [1, 1, 2, 1],       # 1 appears 3/4 times
]
for e in examples:
    from collections import Counter
    c = Counter(e)
    print(f'Array: {e} → majority: {max(c, key=c.get)} (count {max(c.values())})')

Aanpakken vóór Boyer-Moore

Drie aanpakken vóór de optimale aanpak: (1) Sorteren: sorteer de array; het middelste element is altijd het meerderheidselement (omdat het meer dan >n/2 keer voorkomt). O(n log n), O(1) ruimte. (2) Hashmap: tel de frequenties en geef het element terug met een aantal van > n/2. O(n) tijd, O(n) ruimte. (3) Willekeurig steekproeven nemen: kies een willekeurig element en controleer of het >n/2 keer voorkomt; gemiddeld zijn O(1) pogingen nodig (het meerderheidselement wordt gekozen met een kans van >1/2). Boyer-Moore bereikt deterministisch O(n) tijd en O(1) ruimte.

from collections import Counter

def majority_sort(nums):
    nums.sort()
    return nums[len(nums) // 2]  # middle is always majority

def majority_hashmap(nums):
    count = Counter(nums)
    return max(count, key=count.get)

def majority_random(nums):
    import random
    n = len(nums)
    while True:
        candidate = random.choice(nums)
        if nums.count(candidate) > n // 2:
            return candidate

nums = [2, 2, 1, 1, 1, 2, 2]
print(majority_sort(nums[:]))   # 2
print(majority_hashmap(nums))   # 2

Boyer-Moore-stemalgoritme

Het Boyer-Moore-stemalgoritme houdt een candidate en een count bij. Doorloop de array: als count == 0, stel je het huidige element in als de nieuwe kandidaat. Als het huidige element gelijk is aan de kandidaat, verhoog je count. Anders verlaag je count. Aan het einde is de kandidaat het meerderheidselement. Dit werkt omdat het meerderheidselement vaker voorkomt dan alle andere elementen samen — het kan nooit volledig worden weggestemd.

def majority_element(nums):
    candidate = None
    count = 0
    for num in nums:
        if count == 0:
            candidate = num  # new candidate
        if num == candidate:
            count += 1
        else:
            count -= 1
    return candidate

print(majority_element([3, 2, 3]))           # 3
print(majority_element([2, 2, 1, 1, 1, 2, 2]))  # 2
print(majority_element([1]))                 # 1

Intuïtie achter het algoritme

Intuïtie: stel je voor dat elk element één voorkomen van een ander element 'wegstreept'. Het meerderheidselement (count > n/2) heeft meer voorkomens dan alle overige elementen samen, dus het kan alle elementen die niet tot de meerderheid behoren wegstrepen en toch voorkomens overhouden. De variabele count houdt de netto voorsprong van de huidige kandidaat bij. Wanneer count 0 wordt, is de huidige kandidaat door evenveel tegenstanders weggestreept — wie daarna verschijnt, wordt de nieuwe kandidaat.

def bm_trace(nums):
    candidate = count = 0
    for i, num in enumerate(nums):
        if count == 0:
            candidate = num
        old_count = count
        if num == candidate: count += 1
        else: count -= 1
        print(f'num={num}: candidate={candidate}, count: {old_count}→{count}')
    return candidate

bm_trace([2, 2, 1, 1, 1, 2, 2])
# 2→c=1, 2→c=2, 1→c=1, 1→c=0, 1→new cand=1 c=1, 2→c=0, 2→new cand=2 c=1

Bewijs van juistheid

Bewijs: laat m het meerderheidselement zijn met een aantal k > n/2. Kan aan het einde van het algoritme een element dat niet tot de meerderheid behoort de kandidaat zijn? Daarvoor moet m volledig zijn weggestreept. Elke keer dat m wordt weggestreept, kost dat één voorkomen van een ander element. Om alle k voorkomens van m weg te strepen, heb je minstens k voorkomens van elementen die niet m zijn nodig. Maar k > n/2 en het totale aantal elementen die niet m zijn is n-k < n/2 < k. Tegenspraak — m kan niet volledig worden weggestreept.

# Proof by contradiction visualised:
# Array: [M, M, M, A, B, A, B]  (M is majority, 4/7 times)
# Cancellations: M-A, M-B, M-A, M-B would need 4 non-M elements
# But there are only 4 non-M elements and 4 M's > n/2 = 3.5
# So M can survive: after cancellations, at least 1 M remains uncancelled

def verify_bm(tests):
    for nums in tests:
        result = majority_element(nums)
        brute = max(set(nums), key=nums.count)
        assert result == brute, f'Mismatch: {nums} → BM={result}, Brute={brute}'
    print('All tests passed!')

def majority_element(nums):
    c = cnt = 0
    for n in nums:
        if cnt == 0: c = n
        cnt += 1 if n == c else -1
    return c

verify_bm([[1],[3,2,3],[1,1,2,1],[2,2,1,1,1,2,2]])

Meerderheidselement II: meer dan n/3

Meerderheidselement II (LeetCode 229): vind alle elementen die meer dan n/3 keer voorkomen. Hoogstens 2 elementen kunnen hieraan voldoen (want 3 × n/3 = n). Breid Boyer-Moore uit met twee kandidaten en twee tellers. Wanneer een nieuw element met geen van beide kandidaten overeenkomt en beide tellers positief zijn, verlaag je beide tellers. Een laatste verificatiedoorloop bevestigt welke kandidaten echt meer dan n/3 keer voorkomen.

def majority_element_ii(nums):
    cand1 = cand2 = None
    count1 = count2 = 0
    for num in nums:
        if num == cand1: count1 += 1
        elif num == cand2: count2 += 1
        elif count1 == 0: cand1, count1 = num, 1
        elif count2 == 0: cand2, count2 = num, 1
        else:
            count1 -= 1
            count2 -= 1
    # Verify: candidates must exceed n/3
    n = len(nums)
    return [c for c in [cand1, cand2]
            if c is not None and nums.count(c) > n // 3]

print(majority_element_ii([3, 2, 3]))      # [3]
print(majority_element_ii([1, 2]))          # [1, 2]
print(majority_element_ii([1, 1, 1, 3, 3, 2, 2, 2]))  # [1, 2]

Gegeneraliseerd Boyer-Moore: n/k-meerderheid

Boyer-Moore kan worden veralgemeend om alle elementen te vinden die meer dan n/k keer voorkomen, met behulp van k-1 kandidaten. Hoogstens k-1 elementen kunnen aan deze voorwaarde voldoen. Houd k-1 paren (kandidaat, teller) bij. Wanneer geen enkel paar overeenkomt en alle tellers positief zijn, verlaag je alle tellers met 1. Dit gegeneraliseerde algoritme heeft O(n) tijd en O(k) ruimte. Voor sollicitatiegesprekken is het meestal voldoende om de uitbreiding met twee kandidaten voor n/3 te kennen.

def majority_nk(nums, k):
    '''Find all elements appearing more than n/k times.'''
    counts = {}  # candidate -> count
    for num in nums:
        counts[num] = counts.get(num, 0) + 1
        if len(counts) >= k:
            # Remove all candidates by decrementing
            new_counts = {c: cnt-1 for c, cnt in counts.items() if cnt > 1}
            counts = new_counts
    # Verify
    threshold = len(nums) // k
    return [c for c in counts if nums.count(c) > threshold]

print(majority_nk([1,2,3,1,2,1,2,1], 3))  # [1, 2] (both > 8/3 ≈ 2.67)
print(majority_nk([1,1,1,2,2,3,3,3], 4))  # [1, 3] (both > 8/4 = 2)

Meerderheidselement met verdeel en heers

Een verdeel-en-heersaanpak: splits de array in tweeën. Het meerderheidselement van de volledige array moet in minstens één helft een meerderheid zijn (als het in geen van beide helften een meerderheid is, kan het in totaal niet meer dan n/2 keer voorkomen). Zoek recursief het meerderheidselement van elke helft. Als beide helften hetzelfde opleveren, is dat het antwoord. Tel anders beide kandidaten in de volledige array en geef degene met de meeste voorkomens terug. Recursievergelijking: T(n) = 2T(n/2) + O(n) → O(n log n).

def majority_dc(nums, lo=None, hi=None):
    if lo is None: lo, hi = 0, len(nums) - 1
    if lo == hi: return nums[lo]
    mid = (lo + hi) // 2
    left_maj  = majority_dc(nums, lo, mid)
    right_maj = majority_dc(nums, mid + 1, hi)
    if left_maj == right_maj:
        return left_maj
    # Count both candidates across the sub-range
    left_count  = sum(1 for i in range(lo, hi+1) if nums[i] == left_maj)
    right_count = sum(1 for i in range(lo, hi+1) if nums[i] == right_maj)
    return left_maj if left_count > right_count else right_maj

print(majority_dc([3, 2, 3]))            # 3
print(majority_dc([2, 2, 1, 1, 1, 2, 2]))  # 2

Boyer-Moore versus andere methoden

Vergelijking van methoden voor het meerderheidselement: Sorteren: O(n log n) tijd, O(1) ruimte, destructief. Hashmap: O(n) tijd, O(n) ruimte, niet-destructief. Verdeel en heers: O(n log n) tijd, O(log n) ruimte voor de aanroepstack. Boyer-Moore: O(n) tijd, O(1) ruimte, één doorloop, niet-destructief. Boyer-Moore is voor dit probleem strikt beter. Begin in sollicitatiegesprekken altijd met Boyer-Moore nadat je kort de eenvoudigere hashmapaanpak hebt genoemd.

import time, random

nums = [random.randint(1, 100) for _ in range(500000)]
# Make element 42 the majority
nums = [42] * 300000 + nums[:200000]
random.shuffle(nums)

start = time.time()
from collections import Counter
hm = Counter(nums).most_common(1)[0][0]
print(f'HashMap: {hm} in {time.time()-start:.4f}s')

def bm(nums):
    c = cnt = 0
    for n in nums: 
        if cnt == 0: c = n
        cnt += 1 if n == c else -1
    return c

start = time.time()
result = bm(nums)
print(f'Boyer-Moore: {result} in {time.time()-start:.4f}s')
print(f'Both correct: {hm == result}')

Wanneer geen meerderheid gegarandeerd is

Boyer-Moore levert altijd een kandidaat op, maar die is mogelijk geen meerderheidselement als er geen meerderheid bestaat. Als het probleem geen meerderheid garandeert, moet je dit controleren: tel na Boyer-Moore hoe vaak de kandidaat voorkomt. Als count > n/2, is dit de meerderheid. Geef anders -1 of None terug. Deze controle voegt nog een O(n)-doorloop toe, maar de totale aanpak blijft O(n) tijd en O(1) ruimte.

def majority_element_safe(nums):
    '''Returns majority element or None if it doesn't exist.'''
    # Phase 1: find candidate
    candidate = count = 0
    for num in nums:
        if count == 0:
            candidate = num
        count += 1 if num == candidate else -1
    # Phase 2: verify
    if nums.count(candidate) > len(nums) // 2:
        return candidate
    return None

print(majority_element_safe([3, 2, 3]))   # 3 (majority exists)
print(majority_element_safe([1, 2, 3]))   # None (no majority)
print(majority_element_safe([1, 2, 1, 2]))  # None (tie, neither > n/2)

Uitleg voor het sollicitatiegesprek

Aanpak voor een sollicitatiegesprek over het meerderheidselement: (1) Noem sorteren (O(n log n), O(1)) en een hashmap (O(n), O(n)) als eerste aanpakken. (2) Introduceer Boyer-Moore als de optimale oplossing in O(n) tijd en O(1) ruimte. (3) Leg de intuïtie van het wegstrepen uit: de meerderheid kan niet worden weggestreept omdat die vaker voorkomt dan alle andere elementen samen. (4) Schrijf de code helder in 5 regels. (5) Behandel het randgeval: als een meerderheid niet gegarandeerd is, voeg je een verificatiedoorloop toe. Deze structuur laat zien dat je onder tijdsdruk systematisch denkt.

# Clean 5-line Boyer-Moore for interviews
def majority_element(nums):
    c, cnt = nums[0], 1
    for n in nums[1:]:
        cnt += (1 if n == c else -1)
        if cnt == 0: c, cnt = n, 1
    return c

# Verification (if majority not guaranteed)
def majority_with_check(nums):
    c = majority_element(nums)
    return c if nums.count(c) > len(nums) // 2 else -1

print(majority_element([3, 2, 3]))  # 3
print(majority_element([2, 2, 1, 1, 1, 2, 2]))  # 2
print('Time: O(n), Space: O(1)')

Snelle controle

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

Samenvatting van de les

In deze les heb je geleerd: het Boyer-Moore-stemalgoritme vindt het meerderheidselement in O(n) tijd en O(1) ruimte met een kandidaat en teller die elementen die niet tot de meerderheid behoren tegen elkaar wegstrepen, het algoritme kan worden uitgebreid naar een meerderheid van n/3 met twee kandidaten en een verificatiedoorloop nodig heeft wanneer een meerderheid niet gegarandeerd is, en het bewijs steunt op het feit dat het meerderheidselement vaker voorkomt dan alle andere elementen samen, waardoor volledige wegstreping onmogelijk is. Hierna behandelen we de mediaan van twee gesorteerde arrays met binair zoeken naar de partitiegrens.

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 “Meerderheidselement: Boyer-Moore-stemmen” gratis?

Ja — de volledige tekst van “Meerderheidselement: Boyer-Moore-stemmen” 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 “Meerderheidselement: Boyer-Moore-stemmen”?

Vind het element dat meer dan n/2 keer voorkomt met het Boyer-Moore-stemalgoritme in lineaire tijd en met O(1)-ruimte, en bewijs de correctheid ervan. 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 3 van 4.

Hoe lang duurt de les “Meerderheidselement: Boyer-Moore-stemmen”?

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. Divide-and-conquer-sjabloon
  2. Inversies tellen met aangepaste merge sort
  3. Meerderheidselement: Boyer-Moore-stemmen
  4. Mediaan van twee gesorteerde arrays
← Terug naar Voorbereiding op programmeerinterviews