0Pricing
Coding Interview Prep · Aula

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 Coding 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 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 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 crossing

Identificando 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))  # -1

Acompanhando 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))  # True

Encontrando 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))               # -1

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

Contagem 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))  # 4

Reunindo 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 Coding Interview Prep, atualize para CoddyKit PRO. O curso de Coding 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 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 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 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. Busca Binária Clássica: Esquerda, Direita, Meio
  2. Busca Binária em Arrays Rotacionados e Não Ordenados
  3. Limite Inferior e Limite Superior
  4. Busca Binária no Espaço de Respostas
← Voltar para Coding Interview Prep