Busca Binária em Arrays Rotacionados e Não Ordenados
Resolva search-in-rotated-sorted-array e find-minimum-in-rotated-array decidindo qual metade está ordenada em cada etapa.
Busca Binária em Arrays Rotacionados e Não Ordenados é uma aula grátis de DSA Interview Prep no CoddyKit. Esta é a aula 2 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 DSA Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DSA Interview Prep inclui 4 aulas no total.
O que é um vetor ordenado rotacionado?
Um vetor ordenado rotacionado é um vetor ordenado que foi cortado em algum ponto de rotação e teve suas duas partes trocadas. Por exemplo, [4, 5, 6, 7, 0, 1, 2] é o vetor ordenado [0,1,2,4,5,6,7] rotacionado no índice 4. A busca binária padrão falha nesse caso porque o vetor não está mais ordenado globalmente.
A ideia principal é que pelo menos uma metade do vetor está sempre ordenada após qualquer rotação. Sua busca binária precisa identificar qual metade está ordenada antes de decidir para onde mover os limites.
# A rotated sorted array — one half is always sorted
arr = [4, 5, 6, 7, 0, 1, 2]
# Left half [4,5,6,7] is sorted
# Right half [0,1,2] is also sorted
# But left[0]=4 > right[-1]=2 => rotation happened in left-to-right crossingIdentificando a metade ordenada
Depois de calcular mid, compare arr[lo] com arr[mid]. Se arr[lo] <= arr[mid], a metade esquerda está ordenada; caso contrário, a metade direita está ordenada. Depois de saber qual metade está ordenada, você pode verificar se o alvo está dentro desse intervalo ordenado e restringir a busca de acordo.
Essa árvore de decisão permite descartar exatamente metade do vetor a cada etapa, mantendo a complexidade O(log n) mesmo em um vetor rotacionado.
def search_rotated(nums, target):
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
if nums[mid] == target:
return mid
# Left half is sorted
if nums[lo] <= nums[mid]:
if nums[lo] <= target < nums[mid]:
hi = mid - 1
else:
lo = mid + 1
# Right half is sorted
else:
if nums[mid] < target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
return -1
print(search_rotated([4, 5, 6, 7, 0, 1, 2], 0)) # 4
print(search_rotated([4, 5, 6, 7, 0, 1, 2], 3)) # -1Acompanhando um exemplo
Vamos acompanhar search_rotated([4,5,6,7,0,1,2], 0) passo a passo. Inicialmente, lo=0, hi=6, mid=3, arr[mid]=7. O alvo 0 está na metade esquerda ordenada [4..7]? Não, então movemos lo=4. Agora, lo=4, hi=6, mid=5, arr[mid]=1. A metade esquerda [0,1] está ordenada (arr[lo]=0 <= arr[mid]=1). 0 está em [0..1)? Sim, então definimos hi=4. Agora, lo=4, hi=4, mid=4, arr[4]=0 — encontrado no índice 4.
# Step-by-step trace
nums = [4, 5, 6, 7, 0, 1, 2]
target = 0
steps = []
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
steps.append(f'lo={lo} hi={hi} mid={mid} val={nums[mid]}')
if nums[mid] == target:
steps.append(f'Found at {mid}')
break
if nums[lo] <= nums[mid]:
if nums[lo] <= target < nums[mid]:
hi = mid - 1
else:
lo = mid + 1
else:
if nums[mid] < target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
for s in steps:
print(s)Lidando com duplicados na rotação
Quando o vetor rotacionado pode conter duplicados (por exemplo, [1,3,1,1,1]), a condição nums[lo] == nums[mid] é ambígua — não é possível saber qual metade está ordenada. A correção segura é incrementar lo (ou decrementar hi) em um e tentar novamente. Isso degrada o tempo no pior caso para O(n), algo que você deve mencionar ao entrevistador.
def search_rotated_with_dups(nums, target):
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
if nums[mid] == target:
return True
# Ambiguous: shrink left boundary
if nums[lo] == nums[mid] == nums[hi]:
lo += 1
hi -= 1
elif nums[lo] <= nums[mid]:
if nums[lo] <= target < nums[mid]:
hi = mid - 1
else:
lo = mid + 1
else:
if nums[mid] < target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
return False
print(search_rotated_with_dups([1, 3, 1, 1, 1], 3)) # True
print(search_rotated_with_dups([2, 2, 2, 0, 2], 0)) # TrueEncontrando o mínimo em um vetor ordenado rotacionado
Um problema relacionado pede o elemento mínimo em um vetor ordenado rotacionado, sem buscar um alvo específico. O mínimo está sempre na metade não ordenada. A cada etapa: se arr[mid] > arr[hi], o mínimo está na metade direita (lo = mid + 1); caso contrário, está na metade esquerda, incluindo mid (hi = mid). Quando lo == hi, você encontrou o mínimo.
def find_min(nums):
lo, hi = 0, len(nums) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if nums[mid] > nums[hi]:
lo = mid + 1 # min is in right half
else:
hi = mid # min is at mid or left of mid
return nums[lo]
print(find_min([3, 4, 5, 1, 2])) # 1
print(find_min([4, 5, 6, 7, 0, 1, 2])) # 0
print(find_min([11, 13, 15, 17])) # 11 (no rotation)Por que arr[lo] <= arr[mid] identifica a metade esquerda ordenada
A condição arr[lo] <= arr[mid] funciona porque, em um segmento ordenado (ou ordenado sem rotação), o primeiro elemento é sempre o menor. Se arr[lo] <= arr[mid], nenhuma rotação ocorreu dentro de [lo..mid], portanto essa metade está ordenada. A igualdade trata do caso em que lo == mid (um segmento de um único elemento é trivialmente ordenado).
Por outro lado, se arr[lo] > arr[mid], o pivô da rotação deve estar entre lo e mid, o que significa que a metade direita [mid..hi] é o segmento ordenado contínuo.
# Visualise: detect which half is sorted
examples = [
([4, 5, 6, 7, 0, 1, 2], 0, 6), # mid=3, val=7 => left sorted
([6, 7, 0, 1, 2, 4, 5], 0, 6), # mid=3, val=1 => right sorted
]
for arr, lo, hi in examples:
mid = lo + (hi - lo) // 2
if arr[lo] <= arr[mid]:
print(f'arr[{lo}]={arr[lo]} <= arr[{mid}]={arr[mid]} => LEFT half sorted')
else:
print(f'arr[{lo}]={arr[lo]} > arr[{mid}]={arr[mid]} => RIGHT half sorted')Análise de complexidade
Buscar em um vetor ordenado rotacionado usando busca binária continua levando O(log n) de tempo e usando O(1) de espaço, porque ainda reduzimos o espaço de busca pela metade a cada iteração. A única diferença em relação à busca binária clássica é uma verificação adicional, de tempo constante, para identificar qual metade está ordenada.
Com duplicados, o pior caso degrada para O(n), porque podemos incrementar lo em apenas um a cada etapa. Mencione explicitamente essa compensação — isso mostra que você considera casos-limite além do caminho ideal.
Passo a passo do LeetCode 33
LeetCode 33, 'Busca em vetor ordenado rotacionado', é a forma canônica desse problema. As restrições garantem que não há duplicados e que existe exatamente uma rotação. A solução é a função search_rotated que escrevemos anteriormente. Pontos importantes para a entrevista: sempre declare a suposição de que não há duplicados, verifique suas desigualdades com um exemplo concreto no limite e confirme que o índice retornado está correto tanto nos casos em que o elemento é encontrado quanto nos casos em que não é encontrado.
# LeetCode 33 — complete solution
def search(nums, target):
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
if nums[mid] == target:
return mid
if nums[lo] <= nums[mid]: # left half sorted
if nums[lo] <= target < nums[mid]:
hi = mid - 1
else:
lo = mid + 1
else: # right half sorted
if nums[mid] < target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
return -1
# Tests
print(search([4,5,6,7,0,1,2], 0)) # 4
print(search([4,5,6,7,0,1,2], 3)) # -1
print(search([1], 0)) # -1LeetCode 153: Encontrar o mínimo sem duplicados
LeetCode 153, 'Encontrar o mínimo em um vetor ordenado rotacionado', pede o mínimo sem duplicados. A abordagem consiste em comparar arr[mid] com arr[hi] (e não com arr[lo]) para determinar de que lado está o mínimo. Se arr[mid] > arr[hi], o mínimo está à direita; caso contrário, está em mid ou à esquerda. Isso converge para o mínimo em O(log n).
def findMin(nums):
lo, hi = 0, len(nums) - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if nums[mid] > nums[hi]:
lo = mid + 1
else:
hi = mid
return nums[lo]
print(findMin([3,4,5,1,2])) # 1
print(findMin([4,5,6,7,0,1,2])) # 0
print(findMin([11,13,15,17])) # 11Contagem de rotações e índice do pivô
Depois que você consegue encontrar o elemento mínimo, também conhece a contagem de rotações: o índice do mínimo indica exatamente quantas posições o vetor foi rotacionado para a direita. Por exemplo, em [4,5,6,7,0,1,2], o mínimo está no índice 4, portanto o vetor foi rotacionado 4 posições.
Conhecer o pivô permite aplicar a busca binária padrão tratando os índices módulo n: real_idx = (mid + pivot) % n. Essa formulação alternativa pode simplificar o raciocínio ao trabalhar com estruturas indexadas circularmente.
def search_via_pivot(nums, target):
n = len(nums)
# Find pivot (index of minimum)
lo, hi = 0, n - 1
while lo < hi:
mid = lo + (hi - lo) // 2
if nums[mid] > nums[hi]:
lo = mid + 1
else:
hi = mid
pivot = lo
# Binary search with offset
lo, hi = 0, n - 1
while lo <= hi:
mid = lo + (hi - lo) // 2
real_mid = (mid + pivot) % n
if nums[real_mid] == target:
return real_mid
elif nums[real_mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1
print(search_via_pivot([4,5,6,7,0,1,2], 0)) # 4Reunindo tudo
Quando encontrar um problema com um vetor rotacionado em uma entrevista, siga esta árvore de decisão. Primeiro, determine se precisa encontrar um alvo ou encontrar o mínimo. Para encontrar um alvo, use a abordagem de identificação da metade ordenada. Para encontrar o mínimo, compare mid com hi. Se houver possibilidade de duplicados, mencione o pior caso O(n) e adicione a alternativa de redução dos limites.
Pratique acompanhando seu código nos três exemplos clássicos: sem rotação, com uma rotação e com rotação que coloca o mínimo na última posição.
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: um vetor ordenado rotacionado sempre tem pelo menos uma metade ordenada, compare arr[lo] com arr[mid] para identificar qual metade está ordenada antes de decidir onde buscar e para encontrar o mínimo, use arr[mid] em relação a arr[hi] para localizar o pivô da rotação. A seguir, exploraremos as variantes de busca binária de limite inferior e limite superior.
Perguntas Frequentes
A aula “Busca Binária em Arrays Rotacionados e Não Ordenados” é grátis?
Sim — o texto completo de “Busca Binária em Arrays Rotacionados e Não Ordenados” é 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 DSA Interview Prep, atualize para CoddyKit PRO. O curso de DSA Interview Prep inclui 4 aulas no total.
O que vou aprender em “Busca Binária em Arrays Rotacionados e Não Ordenados”?
Resolva search-in-rotated-sorted-array e find-minimum-in-rotated-array decidindo qual metade está ordenada em cada etapa. Você pratica DSA 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 DSA Interview Prep?
Nenhuma experiência prévia é necessária. DSA 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 2 de 4.
Quanto tempo leva a aula “Busca Binária em Arrays Rotacionados e Não Ordenados”?
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 DSA Interview Prep?
Sim. Cada aula de DSA 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
- Busca Binária Clássica: Esquerda, Direita, Meio
- Busca Binária em Arrays Rotacionados e Não Ordenados
- Limite Inferior e Limite Superior
- Busca Binária no Espaço de Respostas