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)) # 2Algoritmo 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])) # 1Intuiçã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=1Prova 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])) # 2Boyer-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
- Modelo de divisão e conquista
- Contar inversões usando ordenamento por intercalação modificado
- Elemento majoritário: votação de Boyer-Moore
- Mediana de dois vetores ordenados