0Pricing
Coding Interview Prep · Aula

Elemento majoritário: votação de Boyer-Moore

Encontre o elemento que aparece mais de n/2 vezes usando o algoritmo de votação de Boyer-Moore, de tempo linear e espaço O(1), e prove sua correção.

Elemento majoritário: votação de Boyer-Moore é uma aula grátis de Coding Interview Prep no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Coding Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Coding Interview Prep inclui 4 aulas no total.

O problema do elemento majoritário

Elemento majoritário (LeetCode 169): encontre o elemento que aparece mais de n/2 vezes em um vetor de comprimento n. O elemento majoritário sempre existe, conforme a garantia do problema. Para [3, 2, 3], a resposta é 3. Para [2, 2, 1, 1, 1, 2, 2], a resposta é 2 (aparece 4 vezes em 7). As abordagens variam desde a ordenação em O(n log n) até o elegante algoritmo de votação de Boyer-Moore, com O(n) de tempo e O(1) de espaço.

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

Abordagens anteriores a Boyer-Moore

Três abordagens anteriores à ideal: (1) Ordenação: ordene o vetor; o elemento central é sempre o majoritário, pois ele aparece mais de n/2 vezes. O(n log n) de tempo e O(1) de espaço. (2) Tabela de dispersão: conte as frequências e retorne o elemento cuja contagem seja > n/2. O(n) de tempo e O(n) de espaço. (3) Amostragem aleatória: escolha um elemento aleatório e verifique se ele aparece >n/2 vezes; o número esperado de tentativas é O(1), pois o elemento majoritário é escolhido com probabilidade >1/2. Boyer-Moore alcança O(n) de tempo e O(1) de espaço de forma determinística.

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

Algoritmo de votação de Boyer-Moore

O algoritmo de votação de Boyer-Moore mantém um candidate e uma count. Percorra o vetor: se count == 0, defina o elemento atual como o novo candidato. Se o elemento atual corresponder ao candidato, incremente a contagem. Caso contrário, decremente a contagem. Ao final, o candidato será o elemento majoritário. Isso funciona porque o elemento majoritário aparece mais vezes do que todos os outros combinados — portanto, nunca pode ser completamente eliminado por votos contrários.

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

Intuição por trás do algoritmo

Intuição: imagine que cada elemento “cancele” uma ocorrência de um elemento diferente. O elemento majoritário (com contagem > n/2) tem mais ocorrências do que todos os outros combinados; assim, pode cancelar todos os elementos não majoritários e ainda conservar ocorrências. A variável count acompanha a vantagem líquida do candidato atual. Quando a contagem chega a 0, o candidato atual foi cancelado por uma quantidade igual de elementos contrários — quem surgir em seguida será o novo candidato.

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

Prova de correção

Prova: seja m o elemento majoritário, com contagem k > n/2. Ao final do algoritmo, um elemento não majoritário pode ser o candidato? Para isso, m teria de ser completamente cancelado. Cada cancelamento de m custa uma ocorrência de algum outro elemento. Para cancelar todas as k ocorrências de m, seriam necessárias pelo menos k ocorrências de elementos diferentes de m. Mas k > n/2, enquanto o total de elementos diferentes de m é n-k < n/2 < k. Contradição — m não pode ser completamente cancelado.

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

Elemento majoritário II: mais de n/3

Elemento majoritário II (LeetCode 229): encontre todos os elementos que aparecem mais de n/3 vezes. No máximo 2 elementos podem satisfazer essa condição, pois 3 × n/3 = n. Estenda Boyer-Moore para manter dois candidatos com duas contagens. Quando um novo elemento não corresponder a nenhum candidato e ambas as contagens forem positivas, decremente ambas. Uma passagem final de verificação confirma quais candidatos realmente ultrapassam n/3.

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]

Boyer-Moore generalizado: maioria n/k

Boyer-Moore pode ser generalizado para encontrar todos os elementos que aparecem mais de n/k vezes usando k-1 candidatos. No máximo k-1 elementos podem satisfazer essa condição. Mantenha k-1 pares (candidato, contagem). Quando nenhum candidato corresponder e todas as contagens forem positivas, decremente todas as contagens em 1. Esse algoritmo generalizado é executado em O(n) de tempo e usa O(k) de espaço. Em entrevistas, geralmente é suficiente conhecer a extensão para dois candidatos (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)

Elemento majoritário por divisão e conquista

Uma abordagem de divisão e conquista: divida o vetor ao meio. O elemento majoritário do vetor completo deve ser majoritário em pelo menos uma das metades; se não for majoritário em nenhuma, não poderá aparecer mais de n/2 vezes no total. Encontre recursivamente o majoritário de cada metade. Se ambas as metades concordarem, essa será a resposta. Caso contrário, conte os dois candidatos no vetor completo e retorne aquele com mais ocorrências. Recorrência: 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 outros métodos

Comparação de métodos para o elemento majoritário: Ordenação: O(n log n) de tempo, O(1) de espaço, destrutiva. Tabela de dispersão: O(n) de tempo, O(n) de espaço, não destrutiva. Divisão e conquista: O(n log n) de tempo, O(log n) de espaço na pilha de chamadas. Boyer-Moore: O(n) de tempo, O(1) de espaço, uma única passagem, não destrutivo. Boyer-Moore é estritamente superior para este problema. Em entrevistas, sempre comece com Boyer-Moore depois de mencionar brevemente a abordagem mais simples com tabela de dispersão.

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

Quando não há garantia de maioria

Boyer-Moore sempre retorna um candidato, mas ele pode não ser um elemento majoritário se nenhum existir. Se o problema não garantir a existência de um elemento majoritário, você deverá verificar: depois de Boyer-Moore, conte as ocorrências do candidato. Se a contagem for > n/2, ele será o majoritário. Caso contrário, retorne -1 ou um valor nulo. Essa verificação adiciona outra passagem O(n), mas mantém o algoritmo geral em O(n) de tempo e O(1) de espaço.

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)

Roteiro para a entrevista

Abordagem para uma entrevista sobre o elemento majoritário: (1) Mencione a ordenação (O(n log n), O(1)) e a tabela de dispersão (O(n), O(n)) como abordagens iniciais. (2) Apresente Boyer-Moore como a solução ideal, O(n) de tempo e O(1) de espaço. (3) Explique a intuição do cancelamento: o majoritário não pode ser cancelado, pois tem mais ocorrências do que todos os outros combinados. (4) Implemente-o de forma clara em 5 linhas. (5) Trate o caso extremo: se a maioria não for garantida, adicione uma passagem de verificação. Essa estrutura demonstra um raciocínio sistemático sob pressão de tempo.

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

Verificação rápida

Teste sua compreensão dos conceitos de Estruturas de Dados e Algoritmos — Preparação para Entrevistas de Programação desta lição.

Recapitulação da lição

Nesta lição, você aprendeu que: a votação de Boyer-Moore encontra o elemento majoritário em O(n) de tempo e O(1) de espaço, usando um candidato e uma contagem que cancelam os elementos não majoritários; o algoritmo se estende à maioria n/3 com dois candidatos e exige uma passagem de verificação quando a maioria não é garantida; e a prova depende do fato de que o elemento majoritário tem mais ocorrências do que todos os outros elementos combinados, tornando impossível o cancelamento completo. A seguir, abordaremos a mediana de dois vetores ordenados usando busca binária no limite da partição.

Perguntas Frequentes

A aula “Elemento majoritário: votação de Boyer-Moore” é grátis?

Sim — o texto completo de “Elemento majoritário: votação de Boyer-Moore” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Coding Interview Prep, atualize para CoddyKit PRO. O curso de Coding Interview Prep inclui 4 aulas no total.

O que vou aprender em “Elemento majoritário: votação de Boyer-Moore”?

Encontre o elemento que aparece mais de n/2 vezes usando o algoritmo de votação de Boyer-Moore, de tempo linear e espaço O(1), e prove sua correção. Você pratica Coding Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Coding Interview Prep?

Nenhuma experiência prévia é necessária. Coding Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Elemento majoritário: votação de Boyer-Moore”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Coding Interview Prep?

Sim. Cada aula de Coding Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Modelo de divisão e conquista
  2. Contar inversões usando ordenamento por intercalação modificado
  3. Elemento majoritário: votação de Boyer-Moore
  4. Mediana de dois vetores ordenados
← Voltar para Coding Interview Prep