Forberedelse til kodeintervjuer · leksjon

Majoritetselement: Boyer-Moore-avstemning

Finn elementet som forekommer mer enn n/2 ganger ved hjelp av Boyer-Moores avstemningsalgoritme, som bruker lineær tid og O(1) ekstra plass, og bevis at den er korrekt.

Leksjon 3 av 413 trinn

Majoritetselement: Boyer-Moore-avstemning er en gratis leksjon i Forberedelse til kodeintervjuer på CoddyKit. Dette er leksjon 3 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Forberedelse til kodeintervjuer, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.

Problemet med majoritetselementet

Majoritetselement (LeetCode 169): Finn elementet som forekommer mer enn n/2 ganger i et array med lengde n. Majoritetselementet finnes alltid, ifølge oppgavens garanti. For [3, 2, 3] er svaret 3. For [2, 2, 1, 1, 1, 2, 2] er svaret 2 (det forekommer 4 av 7 ganger). Metodene varierer fra sortering i O(n log n) til den elegante Boyer-Moore-stemmegivningsalgoritmen med O(n) tid og O(1) plass.

# 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())})')

Metoder før Boyer-Moore

Tre metoder før den optimale: (1) Sortering: sorter arrayet; det midterste elementet er alltid majoritetselementet (siden det forekommer >n/2 ganger). O(n log n), O(1) plass. (2) Hash-tabell: tell frekvensene og returner elementet med antall > n/2. O(n) tid, O(n) plass. (3) Tilfeldig utvalg: velg et tilfeldig element og bekreft at det forekommer >n/2 ganger; forventet O(1) forsøk (majoritetselementet velges med sannsynlighet >1/2). Boyer-Moore oppnår deterministisk O(n) tid og O(1) plass.

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-Moores stemmegivningsalgoritme

Boyer-Moores stemmegivningsalgoritme opprettholder en candidate og en count. Gå gjennom arrayet: Hvis count == 0, settes det gjeldende elementet som den nye kandidaten. Hvis det gjeldende elementet samsvarer med kandidaten, økes count. Ellers reduseres count. Til slutt er kandidaten majoritetselementet. Dette fungerer fordi majoritetselementet forekommer flere ganger enn alle de andre til sammen – det kan aldri stemmes helt ut.

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

Intuisjonen bak algoritmen

Intuisjon: Forestill deg at hvert element «kansellerer» én forekomst av et annet element. Majoritetselementet (count > n/2) har flere forekomster enn alle de andre til sammen, så det kan kansellere alle ikke-majoritetselementene og fortsatt ha forekomster igjen. Variabelen count holder styr på det gjeldende kandidatens netto forsprang. Når count blir 0, er den gjeldende kandidaten kansellert av like mange motstridende elementer – den som dukker opp deretter, blir den nye kandidaten.

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

Korrekthetsbevis

Bevis: La m være majoritetselementet med antall k > n/2. Kan et ikke-majoritetselement være kandidaten på slutten av algoritmen? For at det skal skje, må m ha blitt kansellert fullstendig. Hver kansellering av m krever én forekomst av et annet element. For å kansellere alle k forekomstene av m trenger man minst k forekomster av elementer som ikke er m. Men k > n/2, og det totale antallet ikke-m-elementer er n-k < n/2 < k. En motsigelse – m kan ikke kanselleres fullstendig.

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

Majoritetselement II: mer enn n/3

Majoritetselement II (LeetCode 229): Finn alle elementer som forekommer mer enn n/3 ganger. Høyst to elementer kan oppfylle dette (siden 3 × n/3 = n). Utvid Boyer-Moore til å opprettholde to kandidater med to tellere. Når et nytt element ikke samsvarer med noen av kandidatene, og begge tellerne er positive, reduseres begge. En avsluttende verifiseringsrunde bekrefter hvilke kandidater som faktisk forekommer mer enn n/3 ganger.

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]

Generalisert Boyer-Moore: n/k-majoritet

Boyer-Moore kan generaliseres til å finne alle elementer som forekommer mer enn n/k ganger, ved hjelp av k-1 kandidater. Høyst k-1 elementer kan oppfylle denne betingelsen. Oppretthold k-1 par med (kandidat, teller). Når ingen samsvarer, og alle tellerne er positive, reduseres alle tellerne med 1. Denne generaliserte algoritmen bruker O(n) tid og O(k) plass. I intervjuer er det vanligvis tilstrekkelig å kjenne utvidelsen med to kandidater (n/3).

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)

Majoritetselement med del og hersk

En D&C-tilnærming: Del arrayet i to. Majoritetselementet i hele arrayet må være et majoritetselement i minst én av halvdelene (hvis det ikke er majoritetselement i noen av dem, kan det ikke forekomme mer enn n/2 ganger totalt). Finn majoritetselementet i hver halvdel rekursivt. Hvis halvdelene er enige, er dette svaret. Ellers teller du begge kandidatene i hele arrayet og returnerer den som forekommer flest ganger. Rekurrens: 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 kontra andre metoder

Metodesammenligning for Majoritetselement: Sortering: O(n log n) tid, O(1) plass, destruktiv. Hash-tabell: O(n) tid, O(n) plass, ikke-destruktiv. D&C: O(n log n) tid, O(log n) plass for kallstakken. Boyer-Moore: O(n) tid, O(1) plass, én gjennomgang, ikke-destruktiv. Boyer-Moore er klart best for dette problemet. Presenter alltid Boyer-Moore først i intervjuer, etter å ha nevnt den enklere hash-tabell-tilnærmingen kort.

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}')

Når et majoritetselement ikke er garantert

Boyer-Moore returnerer alltid en kandidat, men kandidaten er kanskje ikke et majoritetselement hvis ingen finnes. Hvis oppgaven ikke garanterer et majoritetselement, må du verifisere: Etter Boyer-Moore teller du kandidatens forekomster. Hvis antallet > n/2, er kandidaten majoritetselementet. Ellers returnerer du -1 eller None. Denne verifiseringen legger til en ny O(n)-gjennomgang, men den samlede algoritmen bruker fortsatt O(n) tid og O(1) plass.

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)

Gjennomgang av intervjuoppgaven

Fremgangsmåte for Majoritetselement i et intervju: (1) Nevn sortering (O(n log n), O(1)) og hash-tabell (O(n), O(n)) som innledende metoder. (2) Introduser Boyer-Moore som den optimale løsningen med O(n) tid og O(1) plass. (3) Forklar kanselleringsintuisjonen: Majoritetselementet kan ikke kanselleres fordi det har flere forekomster enn alle de andre til sammen. (4) Skriv koden ryddig på 5 linjer. (5) Håndter spesialtilfellet: Hvis majoritetselementet ikke er garantert, legger du til en verifiseringsrunde. Denne strukturen viser systematisk tenkning under tidspress.

# 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)')

Kort sjekk

Test forståelsen din av konseptene i Data Structures & Algorithms — Coding Interview Prep fra denne leksjonen.

Oppsummering av leksjonen

I denne leksjonen lærte du at: Boyer-Moores stemmegivning finner majoritetselementet i O(n) tid og O(1) plass ved hjelp av en kandidat og en teller som kansellerer ikke-majoritetselementer, algoritmen utvides til n/3-majoritet med to kandidater og krever en verifiseringsrunde når majoritetselementet ikke er garantert, og beviset bygger på at majoritetselementet forekommer flere ganger enn alle andre elementer til sammen, slik at fullstendig kansellering er umulig. Neste tema er medianen til to sorterte arrayer ved hjelp av binærsøk på partisjonsgrensen.

Gratis å komme i gang

Lær deg Forberedelse til kodeintervjuer med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
90
Leksjoner
360

Ofte stilte spørsmål

Er leksjonen «Majoritetselement: Boyer-Moore-avstemning» gratis?

Ja – hele teksten i «Majoritetselement: Boyer-Moore-avstemning» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Forberedelse til kodeintervjuer-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.

Hva lærer jeg i «Majoritetselement: Boyer-Moore-avstemning»?

Finn elementet som forekommer mer enn n/2 ganger ved hjelp av Boyer-Moores avstemningsalgoritme, som bruker lineær tid og O(1) ekstra plass, og bevis at den er korrekt. Du øver på Forberedelse til kodeintervjuer med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Forberedelse til kodeintervjuer?

Ingen tidligere erfaring er nødvendig. Forberedelse til kodeintervjuer på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Majoritetselement: Boyer-Moore-avstemning»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Forberedelse til kodeintervjuer-leksjonen?

Ja. Alle Forberedelse til kodeintervjuer-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Mal for splitt og hersk
  2. Tell inversjoner med modifisert flettesortering
  3. Majoritetselement: Boyer-Moore-avstemning
  4. Medianen av to sorterte tabeller
← Tilbake til Forberedelse til kodeintervjuer