
Passo 1 de 8
Por que a ordenação permite reduzir a busca
Entenda por que uma lista ordenada permite eliminar regiões inteiras durante uma busca por limites.
Trilha de aprendizado · Nível 14 · Tutorial 5
Use busca binária para localizar posições e intervalos em sequências ordenadas, contabilizando também o custo de ordenar e inserir elementos.
Por que a ordenação permite reduzir a busca
Entenda por que uma lista ordenada permite eliminar regiões inteiras durante uma busca por limites. 2 min
Encontrar os limites com bisect_left e bisect_right
Localize os espaços de inserção antes e depois de valores equivalentes em uma lista ordenada. 3 min
Transformar uma posição em teste de presença
Use a posição candidata retornada por bisect_left para testar presença com segurança. 2 min
Contar repetições e delimitar faixas
Use os dois limites de busca para contar valores repetidos e recortar faixas com contratos inclusivos ou exclusivos. 3 min
Manter a lista ordenada com insort
Insira valores sem quebrar a ordenação e distinga o custo de localizar a posição do custo de deslocar elementos na lista. 2 min
Contabilizar preparação, consultas e resultados
Estime o custo completo de preparar uma sequência ordenada, consultá-la e devolver resultados. 2 min
Escolher a estratégia pelo padrão de uso
Compare varredura, índices hash e listas ordenadas com bisect de acordo com as consultas, atualizações e resultados exigidos. 2 min
Aplicação final: consultar e atualizar uma sequência
Execute um script autocontido para validar presença, contagens e faixas em uma lista ordenada, depois atualize-a sem perder a ordenação. 3 min

Passo 1 de 8
Entenda por que uma lista ordenada permite eliminar regiões inteiras durante uma busca por limites.
Em uma lista de números em ordem crescente, o elemento central divide os candidatos em duas regiões previsíveis. Ao comparar o alvo com esse elemento, uma das metades pode ser descartada com segurança.
Exemplo: se o alvo é menor que o valor central, tudo que está à direita dele também é grande demais. Se o alvo é maior, os valores à esquerda são pequenos demais. A busca continua apenas no intervalo que ainda pode conter a posição procurada.
O alvo 18 é menor que o valor central 21. Por isso, 21 e todos os valores à direita saem da busca; somente a região à esquerda permanece candidata.

Em uma sequência crescente, comparar com o centro permite eliminar uma região inteira.
Esse descarte só é válido se a sequência já estiver ordenada pelo mesmo critério usado na comparação. Neste tutorial, usaremos listas numéricas em ordem crescente, como [3, 8, 12, 17, 21].
O módulo bisect trabalha sobre essa condição: ele não ordena a lista e não verifica se ela realmente está ordenada. Em uma lista fora de ordem, o resultado pode não representar a posição esperada.
Exemplo
Lista ordenada: [4, 9, 15, 22, 31]
Alvo: 12; centro: 15
Como 12 < 15, os valores 15, 22 e 31 podem ser descartados.
Lista fora de ordem: [4, 22, 9, 15, 31]
O centro não divide valores menores e maiores de forma confiável. Descartar uma metade poderia eliminar o 9, que ainda é relevante para localizar o limite de 12.
Dica
Em listas, acessar o elemento central por índice é O(1). Ao reduzir repetidamente o intervalo de candidatos pela metade, a localização de um limite custa O(log n), sem contar o trabalho necessário para preparar ou ordenar os dados.
Na lista ordenada [2, 7, 11, 19, 24, 29, 35], o elemento central observado é 19 e o alvo é 26. Qual região pode ser descartada com segurança nessa comparação?

Passo 2 de 8
Localize os espaços de inserção antes e depois de valores equivalentes em uma lista ordenada.
Importe as funções do módulo padrão bisect:
from bisect import bisect_left, bisect_rightEm uma lista numérica ordenada:
bisect_left(lista, alvo) devolve o espaço antes do primeiro valor igual ao alvo. À esquerda ficam valores menores; à direita, valores maiores ou iguais.bisect_right(lista, alvo) devolve o espaço depois do último valor igual ao alvo. À esquerda ficam valores menores ou iguais; à direita, valores maiores.As duas funções retornam uma posição de inserção; elas não alteram a lista.
Considere idades = [18, 21, 21, 21, 30] e alvo 21.

Para o alvo 21, o limite esquerdo é 1 e o direito é 4. Posições indicam espaços, não necessariamente elementos existentes.
As posições podem coincidir quando não há um valor equivalente.
from bisect import bisect_left, bisect_right
idades = [18, 21, 21, 21, 30]
print(bisect_left(idades, 21)) # 1
print(bisect_right(idades, 21)) # 4
print(bisect_left(idades, 25)) # 4
print(bisect_right(idades, 25)) # 4
print(bisect_left(idades, 10)) # 0
print(bisect_right(idades, 40)) # 5Exemplo
Em idades = [18, 21, 21, 21, 30]:
21, as posições são 1 e 4: há valores equivalentes entre esses dois espaços.25, ambas são 4: o valor entraria entre 21 e 30.10, ambas são 0: o valor entraria antes do primeiro elemento.40, ambas são 5, que é len(idades): o valor entraria após o último elemento.Logo, o resultado sempre pode estar de 0 até len(lista), inclusive. Em [], ambas as funções retornam 0.
Para numeros = [1, 4, 6, 7, 7, 9], bisect_left(numeros, 7) retorna ____.
Para numeros = [1, 4, 6, 7, 7, 9], bisect_right(numeros, 7) retorna ____.
Para numeros = [1, 4, 6, 7, 7, 9], tanto bisect_left(numeros, 8) quanto bisect_right(numeros, 8) retornam 5. Qual interpretação está correta?
Resumo
bisect_left encontra o espaço antes dos valores equivalentes; bisect_right, o espaço depois deles.0 a len(lista), inclusive; em uma lista vazia, ambos retornam 0.
Passo 3 de 8
Use a posição candidata retornada por bisect_left para testar presença com segurança.
bisect_left(valores, alvo) retorna a primeira posição onde o alvo poderia ficar. Isso ainda não prova que ele está na lista.
Para confirmar a presença, use duas condições:
posicao < len(valores));valores[posicao] == alvo).A mesma posição candidata pode apontar para uma ocorrência real ou apenas para o local de inserção.

Quando o alvo não existe, o limite esquerdo pode ser uma posição válida, mas o elemento nela será diferente do alvo.
A ordem das condições evita acessar um índice inexistente.
from bisect import bisect_left
def contem(valores: list[int], alvo: int) -> bool:
posicao = bisect_left(valores, alvo)
return posicao < len(valores) and valores[posicao] == alvo
idades = [18, 25, 25, 31, 42]
print(contem(idades, 25)) # True
print(contem(idades, 30)) # False
print(contem(idades, 99)) # False
print(contem([], 25)) # FalseAtenção
Escrever valores[posicao] == alvo and posicao < len(valores) é inseguro: quando posicao for igual a len(valores), o acesso ocorrerá antes da verificação e poderá gerar IndexError.
Com and, Python só avalia a segunda condição se a primeira for verdadeira. Por isso, verifique o limite antes de acessar a lista.
Organize as etapas para verificar se alvo está em uma lista ordenada usando bisect_left.
Na lista [10, 20, 40], bisect_left(..., 30) retorna uma posição válida. Portanto, 30 está presente na lista.
Resumo
bisect_left para encontrar a primeira posição candidata.len(valores); não a acesse antes de validá-la.posicao < len(valores) and valores[posicao] == alvo.
Passo 4 de 8
Use os dois limites de busca para contar valores repetidos e recortar faixas com contratos inclusivos ou exclusivos.
Em uma lista ordenada, todas as ocorrências de um mesmo valor ficam juntas. Para o alvo 8, bisect_left encontra o início desse bloco e bisect_right encontra a posição logo após seu fim.
Assim, se esquerda = bisect_left(valores, 8) e direita = bisect_right(valores, 8), a quantidade de ocorrências é direita - esquerda. Não é preciso criar uma fatia apenas para contar.
Os marcadores mostram as posições de inserção que envolvem todas as ocorrências de 8.

O intervalo de índices [esquerda, direita) contém exatamente as três ocorrências de 8.
A diferença entre os limites é a contagem.
from bisect import bisect_left, bisect_right
valores = [2, 5, 8, 8, 8, 11, 14]
alvo = 8
esquerda = bisect_left(valores, alvo)
direita = bisect_right(valores, alvo)
quantidade = direita - esquerda
print(quantidade) # 3Para devolver valores entre minimo e maximo, os limites dependem do contrato:
minimo <= valor <= maximo: use bisect_left no mínimo e bisect_right no máximo.minimo <= valor < maximo: use bisect_left nos dois extremos.A fatia sempre exclui seu índice final. O uso de bisect_right no máximo inclusivo faz a fatia terminar depois das ocorrências desse máximo.
Exemplo
from bisect import bisect_left, bisect_right
valores = [2, 5, 8, 8, 8, 11, 14]
# Contrato: 5 <= valor <= 8
inicio = bisect_left(valores, 5) # 1
fim = bisect_right(valores, 8) # 5
print(valores[inicio:fim]) # [5, 8, 8, 8]
# Contrato: 5 <= valor < 8
inicio = bisect_left(valores, 5) # 1
fim = bisect_left(valores, 8) # 2
print(valores[inicio:fim]) # [5]Dica
Se os dois limites coincidirem, a fatia é vazia: valores[p:p] resulta em []. Defina e valide o contrato da consulta para que minimo <= maximo; se o mínimo for maior que o máximo, os limites deixam de representar uma faixa válida.
Considere inicio = bisect_left(valores, minimo). Associe cada objetivo ao limite final adequado.
Toque em um item e depois no par correspondente.
Para valores = [1, 4, 4, 4, 9], bisect_left(valores, 4) retorna 1 e bisect_right(valores, 4) retorna 4. Complete: quantidade = 4 - 1 = ____.
Uma lista ordenada tem n elementos. Uma faixa retorna k valores. Qual afirmação está correta?

Passo 5 de 8
Insira valores sem quebrar a ordenação e distinga o custo de localizar a posição do custo de deslocar elementos na lista.
Além de localizar limites, o módulo bisect oferece insort_left e insort_right. Elas encontram a posição adequada e inserem o valor na própria lista; o retorno é None.
Use insort_left para inserir antes dos valores equivalentes e insort_right para inserir depois deles. As duas preservam a ordem crescente, desde que a lista já esteja ordenada pelo mesmo critério.
As duas variantes diferem apenas no ponto escolhido dentro de um bloco de valores iguais.

Com valores iguais, left escolhe o limite esquerdo; right escolhe o limite direito.
Execute o exemplo e observe que a lista é alterada após cada chamada.
from bisect import insort_left, insort_right
valores = [2, 5, 5, 8]
insort_left(valores, 5)
print(valores) # [2, 5, 5, 5, 8]
insort_right(valores, 5)
print(valores) # [2, 5, 5, 5, 5, 8]A busca binária usada para encontrar o limite custa O(log n). Porém, uma lista é contígua: para abrir espaço no meio ou no início, os elementos à direita precisam ser deslocados.
Por isso, insort_left e insort_right têm custo O(n) no pior caso. A etapa de busca é rápida, mas o deslocamento pode envolver quase toda a lista.
Inserir 4 antes de 5 exige mover 5, 7 e 9 uma posição para a direita.

O deslocamento dos elementos, e não a localização do índice, domina o pior caso.
Dica
Se a identidade ou a ordem relativa entre itens equivalentes importar, escolha conscientemente insort_left ou insort_right. Para números iguais isolados, a lista resultante terá os mesmos valores visíveis, mas a posição escolhida ainda é diferente.
Atenção
valores.append(4) em uma lista como [1, 3, 5] produz [1, 3, 5, 4], que deixou de estar ordenada. Depois disso, resultados de bisect_left, bisect_right e insort não têm a garantia esperada.
Exemplo
from bisect import bisect_left, insort_right
valores = [1, 3, 5]
insort_right(valores, 4)
print(valores) # [1, 3, 4, 5]
print(bisect_left(valores, 4)) # 2Como a inserção preservou a ordenação, a busca posterior continua válida.
Uma lista ordenada tem n elementos. Qual afirmação explica corretamente o custo de insort_left(lista, valor) ao inserir perto do início?
Você quer inserir um novo registro com chave numérica 10 depois de todos os registros que já têm chave 10, mantendo a lista ordenada. Qual chamada é adequada?

Passo 6 de 8
Estime o custo completo de preparar uma sequência ordenada, consultá-la e devolver resultados.
O custo de bisect só descreve a localização de um limite em uma sequência que já está ordenada. Se os dados chegam sem ordem, inclua a preparação no cenário.
duracoes_ordenadas = sorted(duracoes)Para n elementos, sorted custa O(n log n) no pior caso. Como ela cria uma nova lista, também requer O(n) de armazenamento adicional para essa lista ordenada.
A ordenação é uma etapa anterior às consultas, não um detalhe gratuito da busca.

Quando a entrada não está ordenada, contabilize a criação da sequência ordenada antes de chamar bisect.
Exemplo
Se você ordenar duracoes uma vez e fizer várias consultas depois, a preparação pode ser compartilhada.
Já se cada uso recebe dados não ordenados e executa sorted(duracoes) de novo, o custo O(n log n) volta a fazer parte de cada uso. A fronteira da análise deve corresponder ao trabalho que o programa realmente executa.
Sem atualizações e sem criar fatias, m consultas de limites em uma lista preparada custam:
O(n log n + m log n)
O primeiro termo é a ordenação inicial; o segundo reúne as m localizações com bisect. Se a lista já chega ordenada pelo critério correto, o termo de preparação não entra nessa operação.
Exemplo
Considere uma faixa delimitada assim:
inicio = bisect_left(valores, minimo)
fim = bisect_right(valores, maximo)
faixa = valores[inicio:fim]Encontrar inicio e fim custa O(log n). Porém, criar faixa copia os elementos devolvidos. Se há k elementos na faixa, essa materialização custa O(k) de tempo e requer O(k) de memória adicional.
Em várias consultas que retornam fatias, inclua o total de elementos copiados nas saídas; não basta contar as buscas binárias.
Dica
Se você só precisa dos índices inicio e fim, não crie a fatia. Assim, a consulta permanece na etapa de localização; a cópia dos resultados só existe quando ela é necessária.
insort_left e insort_right encontram a posição em O(log n), mas inserir em uma lista pode deslocar elementos à direita. No pior caso, cada inserção custa O(n).
Portanto, muitas inserções acumulam custos de deslocamento. Manter a lista ordenada não torna as atualizações automaticamente logarítmicas.
Associe cada fase ao custo predominante descrito.
Toque em um item e depois no par correspondente.
Se uma consulta recebe uma lista não ordenada e devolve uma fatia de resultados, é suficiente comparar apenas o O(log n) das chamadas a bisect.

Passo 7 de 8
Compare varredura, índices hash e listas ordenadas com bisect de acordo com as consultas, atualizações e resultados exigidos.
Não existe uma estrutura sempre mais rápida. Primeiro preserve o contrato da consulta: você precisa apenas saber se um valor existe, obter um registro por chave, localizar sua posição ordenada ou trazer todos os valores de uma faixa?
Depois considere a carga completa: preparação, memória extra, quantidade de consultas, atualizações e tamanho dos resultados devolvidos.

A forma da consulta orienta a escolha mais do que o nome da estrutura.
Exemplo
Dica
Uma faixa pode ser localizada com dois limites em O(log n), mas criar a fatia com k elementos adiciona O(k) de tempo e memória para os resultados. Muitas atualizações também podem tornar a manutenção da lista ordenada cara.
Relacione cada cenário à estratégia que tende a ser a melhor candidata.
Toque em um item e depois no par correspondente.
Uma aplicação mantém durações inteiras de atendimentos. Ela recebe poucas novas durações por dia, mas faz muitas consultas como “quantos atendimentos ficaram entre 20 e 35 minutos?” e às vezes exibe a lista desses atendimentos. Qual estratégia você escolheria e por quê?
Escreva pelo menos 80 caracteres (0/80).

Passo 8 de 8
Execute um script autocontido para validar presença, contagens e faixas em uma lista ordenada, depois atualize-a sem perder a ordenação.
Neste exemplo, as durações são preparadas uma vez com sorted. As consultas usam limites binários; só a faixa solicitada é materializada como uma nova lista. Copie o script para um arquivo .py no seu computador e execute-o.
Dados, consultas, verificações e atualização em um único arquivo.
from bisect import bisect_left, bisect_right, insort_right
def presente(valores, alvo):
posicao = bisect_left(valores, alvo)
return posicao < len(valores) and valores[posicao] == alvo
def contar(valores, alvo):
return bisect_right(valores, alvo) - bisect_left(valores, alvo)
def faixa_inclusiva(valores, minimo, maximo):
if minimo > maximo:
raise ValueError("o mínimo não pode ser maior que o máximo")
inicio = bisect_left(valores, minimo)
fim = bisect_right(valores, maximo)
return valores[inicio:fim]
# Preparação: sorted cria uma nova lista ordenada.
duracoes_originais = [60, 30, 90, 15, 30, 45]
duracoes = sorted(duracoes_originais)
# Presença: posição candidata + limite válido + igualdade.
assert presente([], 30) is False
assert presente(duracoes, 30) is True
assert presente(duracoes, 50) is False
assert presente(duracoes, 120) is False
# Repetições e faixas, incluindo extremos e resultado vazio.
assert contar(duracoes, 30) == 2
assert contar(duracoes, 50) == 0
assert faixa_inclusiva(duracoes, 15, 15) == [15]
assert faixa_inclusiva(duracoes, 20, 60) == [30, 30, 45, 60]
assert faixa_inclusiva(duracoes, 91, 120) == []
print("Antes da inserção:", duracoes)
print("Faixa de 20 a 60:", faixa_inclusiva(duracoes, 20, 60))
# Atualização: insort_right encontra o limite e insere na própria lista.
insort_right(duracoes, 30)
assert duracoes == [15, 30, 30, 30, 45, 60, 90]
assert contar(duracoes, 30) == 3
assert faixa_inclusiva(duracoes, 20, 60) == [30, 30, 30, 45, 60]
print("Depois da inserção:", duracoes)
print("Ocorrências de 30:", contar(duracoes, 30))
print("Faixa de 20 a 60:", faixa_inclusiva(duracoes, 20, 60))Dica
Se nenhuma mensagem de erro aparecer, todos os assert passaram. A lista inicial é preparada com ordenação; depois, insort_right preserva essa condição ao inserir outro 30 após os equivalentes.
A consulta de faixa encontra dois índices; a fatia usa o início inclusivo e o fim exclusivo.

Os limites localizam a faixa; a fatia materializa apenas os elementos entre eles.
bisect_left produz uma posição onde o alvo poderia começar, não uma prova de presença. Por isso presente confirma duas condições: a posição existe na lista e o elemento nessa posição é igual ao alvo. Já contar não cria uma fatia: calcula somente direita - esquerda.
Atenção
A busca pelos limites custa O(log n), mas sorted custa O(n log n) quando os dados chegam desordenados. A inserção em uma lista pode deslocar elementos e custar O(n). E a fatia retornada por faixa_inclusiva custa O(k) em tempo e memória adicional para os k resultados.
Execute o script e relate o que ocorreu com a faixa de 20 a 60 antes e depois da inserção. Em seguida, explique por que uma posição de bisect_left não basta para afirmar presença e justifique quando esta estratégia é adequada.
Escreva pelo menos 180 caracteres (0/180).
Resumo
Use a estrutura que atende ao resultado exigido pelo padrão real de uso.
bisect.bisect_left e uma verificação de limite mais igualdade para testar presença com segurança.insort_left e insort_right preservam a ordem, mas a inserção em lista pode deslocar elementos em O(n).Parabéns! Você concluiu: Buscar em sequências ordenadas com bisect
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks