Límite inferior y límite superior
Implemente bisect_left y bisect_right desde cero y aplíquelos para encontrar la primera y la última posición de un valor objetivo.
Límite inferior y límite superior es una lección gratuita de Coding Interview Prep en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Coding Interview Prep, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Coding Interview Prep incluye 4 lecciones en total.
¿Qué son las cotas inferior y superior?
La cota inferior de un valor objetivo en un array ordenado es el índice del primer elemento mayor o igual que el objetivo (a menudo se denomina bisect_left). La cota superior es el índice del primer elemento estrictamente mayor que el objetivo (bisect_right). Juntas delimitan todas las apariciones del objetivo y permiten realizar consultas de rangos en O(log n).
Estas dos operaciones son la base de muchos problemas de entrevistas: contar apariciones, encontrar rangos, determinar posiciones de inserción y mucho más.
arr = [1, 2, 2, 2, 3, 5]
# lower bound of 2 => index 1 (first element >= 2)
# upper bound of 2 => index 4 (first element > 2)
# occurrences of 2 => upper - lower = 4 - 1 = 3
print('lower bound of 2:', 1)
print('upper bound of 2:', 4)
print('count of 2:', 4 - 1)Implementar la cota inferior (bisect_left)
bisect_left(arr, x) devuelve el índice más a la izquierda i tal que arr[i] >= x, o len(arr) si todos los elementos son menores. La implementación usa un límite superior exclusivo: hi = len(arr), la condición del bucle lo < hi y la actualización hi = mid cuando arr[mid] >= x. Esto garantiza que la respuesta converja a la primera posición válida.
def bisect_left(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid # arr[mid] >= x, so potential answer
return lo # lo == hi == insertion point
arr = [1, 2, 2, 2, 3, 5]
print(bisect_left(arr, 2)) # 1
print(bisect_left(arr, 0)) # 0 (before all)
print(bisect_left(arr, 6)) # 6 (after all)
print(bisect_left(arr, 3)) # 4Implementar la cota superior (bisect_right)
bisect_right(arr, x) devuelve el índice más a la izquierda i tal que arr[i] > x. Solo cambia una línea respecto a bisect_left: la condición cambia de arr[mid] < x a arr[mid] <= x. Cuando arr[mid] <= x, la respuesta está estrictamente a la derecha de mid, por lo que establecemos lo = mid + 1; de lo contrario, acotamos desde la derecha.
def bisect_right(arr, x):
lo, hi = 0, len(arr)
while lo < hi:
mid = lo + (hi - lo) // 2
if arr[mid] <= x:
lo = mid + 1 # arr[mid] <= x, so answer is strictly right
else:
hi = mid
return lo
arr = [1, 2, 2, 2, 3, 5]
print(bisect_right(arr, 2)) # 4
print(bisect_right(arr, 0)) # 0
print(bisect_right(arr, 5)) # 6
print(bisect_right(arr, 4)) # 5Contar apariciones con ambas cotas
Para contar las apariciones de un objetivo en un array ordenado en O(log n), aplique ambas cotas: count = bisect_right(arr, target) - bisect_left(arr, target). Si el recuento es 0, el objetivo no está presente. Esto es considerablemente más rápido que un recorrido lineal y constituye el enfoque estándar para las consultas de frecuencia sobre datos ordenados.
import bisect
def count_occurrences(arr, target):
left = bisect.bisect_left(arr, target)
right = bisect.bisect_right(arr, target)
return right - left
arr = [1, 2, 2, 2, 3, 3, 5]
print(count_occurrences(arr, 2)) # 3
print(count_occurrences(arr, 3)) # 2
print(count_occurrences(arr, 4)) # 0
print(count_occurrences(arr, 1)) # 1Encontrar la primera y la última posición del objetivo
LeetCode 34 'Find First and Last Position of Element in Sorted Array' solicita devolver [first_idx, last_idx] en O(log n). La primera posición es bisect_left(arr, target), pero solo si arr[result] == target. La última posición es bisect_right(arr, target) - 1. Si alguna de las comprobaciones falla, devuelva [-1, -1].
import bisect
def search_range(nums, target):
left = bisect.bisect_left(nums, target)
if left == len(nums) or nums[left] != target:
return [-1, -1]
right = bisect.bisect_right(nums, target) - 1
return [left, right]
print(search_range([5,7,7,8,8,10], 8)) # [3, 4]
print(search_range([5,7,7,8,8,10], 6)) # [-1, -1]
print(search_range([], 0)) # [-1, -1]Posición de inserción (LeetCode 35)
LeetCode 35 'Search Insert Position' plantea la siguiente pregunta: ¿dónde se insertaría el objetivo para mantener el array ordenado? Esto es exactamente bisect_left(arr, target). Si el objetivo existe, bisect_left devuelve su índice. Si no existe, bisect_left devuelve el índice donde se insertaría. No se necesita ningún tratamiento especial: la misma función gestiona ambas situaciones.
import bisect
def searchInsert(nums, target):
return bisect.bisect_left(nums, target)
print(searchInsert([1,3,5,6], 5)) # 2 (exists at index 2)
print(searchInsert([1,3,5,6], 2)) # 1 (would insert between 1 and 3)
print(searchInsert([1,3,5,6], 7)) # 4 (would append at end)
print(searchInsert([1,3,5,6], 0)) # 0 (would prepend)La diferencia entre bisect_left y bisect_right
Cuando no existen duplicados, bisect_left y bisect_right devuelven el mismo índice. La diferencia solo importa cuando el objetivo aparece varias veces. bisect_left señala la primera ocurrencia; bisect_right señala la posición posterior a la última ocurrencia. Elija siempre una u otra según necesite insertar antes de las ocurrencias existentes (izquierda) o después de ellas (derecha).
import bisect
arr = [1, 2, 2, 2, 3]
# Insert a new 2 before all existing 2s
print(bisect.bisect_left(arr, 2)) # 1
# Insert a new 2 after all existing 2s
print(bisect.bisect_right(arr, 2)) # 4
# For a value not in array, both give same insertion point
print(bisect.bisect_left(arr, 2.5)) # 4
print(bisect.bisect_right(arr, 2.5)) # 4Aplicar cotas a consultas de frecuencia ordenadas
Cuando necesite responder de forma eficiente a muchas consultas de frecuencia por rangos sobre un array ordenado, ordene el array una sola vez y use bisect para cada consulta. Cada consulta responde a «¿cuántos elementos se encuentran en [lo, hi]?» en O(log n), en lugar de O(n). Este patrón aparece en problemas sobre el recuento de elementos dentro de un rango de valores después de ordenar.
import bisect
def count_in_range(arr, lo, hi):
'''Count elements in arr with lo <= val <= hi. arr must be sorted.'''
left = bisect.bisect_left(arr, lo)
right = bisect.bisect_right(arr, hi)
return right - left
arr = sorted([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
print(arr) # [1,1,2,3,3,4,5,5,5,6,9]
print(count_in_range(arr, 3, 5)) # 6 (3,3,4,5,5,5)
print(count_in_range(arr, 1, 2)) # 3 (1,1,2)Búsqueda binaria con clave personalizada
A veces, la clave de búsqueda no es el valor almacenado, sino una propiedad derivada. El módulo bisect de Python no admite directamente una función de clave, pero puede realizar la búsqueda binaria manualmente aplicando la clave dentro del bucle. Este patrón aparece al buscar en una lista de objetos mediante uno de sus atributos.
# Binary search on a list of (score, name) tuples by score
def lower_bound_by_score(records, min_score):
lo, hi = 0, len(records)
while lo < hi:
mid = lo + (hi - lo) // 2
if records[mid][0] < min_score:
lo = mid + 1
else:
hi = mid
return lo
records = [(50, 'Alice'), (72, 'Bob'), (72, 'Carol'), (88, 'Dave'), (95, 'Eve')]
idx = lower_bound_by_score(records, 72)
print(idx) # 1 (first record with score >= 72)
print(records[idx:]) # [(72,'Bob'),(72,'Carol'),(88,'Dave'),(95,'Eve')]Errores habituales en entrevistas con cotas
El error más común es olvidar validar el resultado después de llamar a bisect_left. La función siempre devuelve un índice de inserción válido, pero no garantiza que el elemento de ese índice sea igual al objetivo. Compruebe siempre arr[result] == target antes de dar por hecho que se encontró el objetivo.
Un segundo error consiste en usar bisect_right cuando se quiere obtener la primera ocurrencia: bisect_right devuelve la posición posterior a la última ocurrencia, por lo que restar 1 proporciona la última, no la primera.
import bisect
arr = [1, 3, 5, 7]
target = 4
# bisect_left returns 2 (insertion point for 4 between 3 and 5)
idx = bisect.bisect_left(arr, target)
print(idx) # 2
# Validate: arr[2] is 5, not 4 => target absent
found = idx < len(arr) and arr[idx] == target
print('Found:', found) # FalseResumen: cuándo usar bisect_left y bisect_right
Use bisect_left cuando necesite: la primera ocurrencia del objetivo, el punto de inserción que desplaza las ocurrencias existentes hacia la derecha o comprobar si existe el objetivo. Use bisect_right cuando necesite: la posición posterior a la última ocurrencia, el punto de inserción después de todas las ocurrencias existentes o el recuento de elementos <= target (es igual a bisect_right(arr, target)).
Ambas se ejecutan en O(log n) y forman parte de la biblioteca estándar de Python, por lo que puede importarlas y usarlas directamente, a menos que el entrevistador le pida implementarlas desde cero.
Comprobación rápida
Compruebe su comprensión de los conceptos de Data Structures & Algorithms — Coding Interview Prep de esta lección.
Resumen de la lección
En esta lección ha aprendido que: bisect_left encuentra el primer elemento >= target, bisect_right encuentra el primer elemento > target (la posición posterior a la última ocurrencia) y su diferencia proporciona el recuento de apariciones en O(log n). A continuación, exploraremos la búsqueda binaria en el espacio de respuestas, donde el espacio de búsqueda es un rango de posibles respuestas, no un índice de array.
Preguntas frecuentes
¿La lección «Límite inferior y límite superior» es gratis?
Sí — el texto completo de «Límite inferior y límite superior» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Coding Interview Prep, actualiza a CoddyKit PRO. El curso de Coding Interview Prep incluye 4 lecciones en total.
¿Qué aprenderé en «Límite inferior y límite superior»?
Implemente bisect_left y bisect_right desde cero y aplíquelos para encontrar la primera y la última posición de un valor objetivo. Practicas Coding Interview Prep con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Coding Interview Prep?
No se requiere experiencia previa. Coding Interview Prep en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.
¿Cuánto tiempo toma la lección «Límite inferior y límite superior»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Coding Interview Prep?
Sí. Cada lección de Coding Interview Prep incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Búsqueda binaria clásica: izquierda, derecha y centro
- Búsqueda binaria en arrays rotados y sin ordenar
- Límite inferior y límite superior
- Búsqueda binaria sobre el espacio de respuestas