Análisis del problema de búsqueda binaria

Análisis del problema de búsqueda binaria

Utilice el marco de búsqueda binaria para analizar y resolver los problemas de algoritmos clásicos en leetcode/likou.

Dos formas de escribir búsqueda binaria

Método de escritura de intervalo cerrado a la izquierda-cerrado a la derecha [left, right](recomendado)

Los ejemplos posteriores tienen prioridad para usar esta forma de escribir.

# the num array to search is "f"
# 定义区间[left, right]
left = 0
right = len(f)-1

# 先看什么时候区间[left, right]不含有任何元素,注意left==right的时候,区间内是有一个元素f[left] 也就是f[right]的,所以要区间内没有元素,必须left>right,而最小满足条件的left就是right+1
# the stop condition is left > right ==> left == right + 1 ([left, left - 1] range has no element)
# 因此将区间内没有元素的条件(left>right)取反,即left<=right
# therefore the while codition is left <= right
while left <= right:
    mid = (left + right) // 2
    mid_element = f[mid]
    if mid_element == target:
        ...
    elif mid_element < target:
        ...
    elif mid_element > target:
        ...

# to fill "..." part
# (1) if want to search left side [left, mid-1], use:
right = mid - 1
# 搜左边半区间(不含mid),因为是闭区间,所以直接设置为right=mid-1
# (2) if want to search right side [mid+1, right], use:
left = mid + 1
# 搜右边半区间(不含mid),因为是闭区间,所以直接设置为left=mid+1
# 当问题是搜索左边界(最小满足条件的位置)在mid_element == target时,需要继续向左搜索,因为当前满足条件的可能不是最小的位置,所以仍要right=mid-1;反过来,当问题是搜索左=右边界(最大满足条件的位置)在mid_element == target时,需要继续向右搜索,因为当前满足条件的可能不是最大的位置,所以仍要left=mid+1;

# check if out of bound
# 如果左指针超过了f的最右侧边界,说明target太大,没有搜索到;或者右指针超过了f的左侧边界,说明target太小,,没有搜索到
if left > len(f) - 1 or right < 0:
    return NOT_FOUND
# 很好记,定义的区间是[left, right],所以左边界(或者等值元素)的结果就是left,右边界是right
# result == left == right+1 (search left bound)
# result == right == left-1 (search right bound)
# 最后检查是不是满足搜索需求,如果刚好卡在两个元素中间,虽然不越界,也是没搜索到
if f[result] == target
    return result
else:
    return NOT_FOUND

Método de escritura de intervalos cerrados a la izquierda y abiertos a la derecha [left, right)(tradicional)

# the num array to search is "f"
# 定义区间[left, right)
left = 0
right = len(f)

# 先看什么时候区间[left, right)不含有任何元素,左闭右开需要left==right
# the stop condition is left == right ([left,. left) range has no element)
# 因此while条件是left < right
# therefore the while coindition is left < right
while left < right:
    mid = (left + right) // 2
    mid_element = f[mid]
    if mid_element == target:
        ...
    elif mid_element < target:
        ...
    elif mid_element > target:
        ...

# to fill "..." part
# (1) if want to search left side [left, mid), use:
right = mid
# (2) if want to search right side [mid+1, right), use:
left = mid + 1
# when searching left / right bound, go to left / right side when mid_element == target

# check if out of bound
if left > len(f) - 1 or right <= 0:
    return NOT_FOUND
# 看定义的区间是[left, right),right是不在范围内的,范围内最大的值是right-1
# result == left == right (search left bound)
# result == right-1 == left-1 (search right bound)
# check element
if f[result] == target
    return result
else:
    return NOT_FOUND

varias preguntas basicas

Buscar elemento, Buscar borde izquierdo, Buscar borde derecho. Estas preguntas pueden ser subprocesos de otras preguntas.

Búsqueda binaria LC-704

Dada una matriz ordenada (ascendente) de números enteros de n elementos y un objetivo de valor objetivo, escriba una función para buscar el objetivo en números y devuelva el subíndice si existe el valor objetivo; de lo contrario, devuelva -1.

def search(self, nums: List[int], target: int) -> int:
    left = 0
    right = len(nums) - 1
    while left <= right:
        mid = int((left + right) / 2)
        if nums[mid] == target:
            return mid
        elif nums[mid] > target:
            right = mid - 1
        elif nums[mid] < target: 
            left = mid + 1
    return -1

LC-34 Encuentra la primera y última posición de un elemento en una matriz ordenada

Se le proporciona una matriz de números enteros en orden no decreciente y un objetivo de valor objetivo. Averigüe dónde comienza y termina el valor objetivo dado en la matriz. Si el objetivo del valor objetivo no existe en la matriz, devuelve [-1, -1].
Debe diseñar e implementar un algoritmo con complejidad de tiempo O(log n) para resolver este problema.

Análisis:
la implicación de logn es obviamente usar el método binario; encontrar la posición inicial y la posición final del elemento son en realidad dos problemas: búsqueda binaria para encontrar el límite izquierdo, búsqueda binaria para encontrar el límite derecho

def searchRange(self, nums: List[int], target: int) -> List[int]:
    # 二分搜索寻找左边界
    res_left = -1

    left = 0
    right = len(nums) - 1
    while left <= right:
        mid = int((left + right) / 2)
        if nums[mid] == target:  # still go left side since getting left bound
            right = mid - 1
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
    # if out of bound left > right = len(nums)-1, means it is not found
    if left > len(nums) - 1:
        res_left = -1
    else:
        if nums[left] == target:
            res_left = left
        else:
            res_left = -1


    # 二分搜索寻找右边界
    res_right = -1

    left = 0
    right = len(nums) - 1
    while left <= right:
        mid = int((left + right) / 2)
        if nums[mid] == target:  # still go right side since getting right bound
            left = mid + 1
        elif nums[mid] < target:
            left = mid + 1
        elif nums[mid] > target:
            right = mid - 1
    # if out of bound right < left  = 0, means it is not found
    if right < 0:
        res_right = -1
    else:
        if nums[right] == target:
            res_right = right
        else:
            res_right = -1

    return [res_left, res_right]

encontrar la posición del símbolo de intercalación

Dada una matriz de enteros ordenada nums y un objetivo de valor objetivo de entero, busque el objetivo en la matriz y devuelva su índice. Si el valor de destino no existe en la matriz, devuelve la posición en la que se insertará en orden.
Utilice un algoritmo con complejidad de tiempo O (log n).

def searchInsert(self, nums, target: int) -> int: # Of-068
    left = 0
    right = len(nums) - 1
    while left <= right:
        mid = int((left + right)/2)
        if nums[mid] == target:
            return mid
        elif nums[mid] > target:
            right = mid - 1
        elif nums[mid] < target:
            left = mid + 1
    return left

Problema de deformación de la búsqueda binaria

LC-74 Búsqueda de matriz bidimensional

Escriba un algoritmo eficiente para determinar si hay un valor objetivo en la matriz mxn. La matriz tiene las siguientes características:
los enteros de cada fila están dispuestos en orden ascendente de izquierda a derecha, y el primer entero de cada fila es mayor que el último entero de la fila anterior.

Idea de análisis 1:
primera fila de búsqueda binaria, luego columna de búsqueda binaria

def searchMatrix(self, matrix, target: int) -> bool: # LC-74
    # 先搜索row 再搜索column
    mat_row_count = len(matrix) 
    mat_col_count = len(matrix[0])

    # 二分搜索行
    row_res = -1

    row_left = 0
    row_right = mat_row_count - 1
    # 元素在某一行的条件是该行的最左边大于等于元素且该行的右边小于等于元素
    while row_left <= row_right: 
        mid = int( (row_left + row_right) / 2)
        if matrix[mid][0] > target:
            row_right = mid - 1
        elif matrix[mid][-1] < target:
            row_left = mid + 1
        else:
            row_res = mid
            break

    if row_res == -1:
        return False

    # 二分搜索列,等同于常规的一维二分搜索
    col_left = 0
    col_right = mat_col_count - 1

    while col_left <= col_right:
        mid = int( (col_left + col_right) /2)
        if matrix[row_res][mid] == target:
            return True
        elif matrix[row_res][mid] > target:
            col_right = mid - 1
        elif matrix[row_res][mid] < target:
            col_left = mid + 1

    return False

Idea de análisis 2:
aplane la matriz en una matriz, aún satisfaciendo la monotonicidad, y use directamente fila, col = divmod (ind, n) para completar la conversión del índice aplanado y el índice fila-columna.

def searchMatrix(self, matrix, target: int) -> bool: # flatten
    m = len(matrix)
    n = len(matrix[0])
    search_range_lower = 0
    search_range_upper = m * n - 1

    left = search_range_lower
    right = search_range_upper
    while left <= right:
        mid = (left + right) // 2
        mid_row, mid_col = divmod(mid, n)
        mid_val = matrix[mid_row][mid_col]
        if mid_val == target:
            return True
        elif mid_val > target:
            right = mid - 1
        elif mid_val < target:
            left = mid + 1

    return False

Cualquier función monótonamente no creciente/monótonamente no decreciente puede usar la búsqueda binaria

Dado k para encontrar el valor x de f(x)=k para resolver el problema de la ecuación, si f(x) es una función monótona no creciente/monótona no decreciente, entonces se puede usar la búsqueda binaria. El proceso es generalmente:

  • Escribe la expresión para f(x)
  • Determinar los límites superior e inferior de la búsqueda.
  • Resolver problemas utilizando los tres problemas básicos de búsqueda binaria como subprocesos

Faltan dígitos del 0 al n-1

Todos los números en una matriz ordenada ascendente de longitud n-1 son únicos y cada número está en el rango de 0 a n-1. Entre los n números en el rango 0 ~ n-1, solo hay un número que no está en la matriz, encuentre este número.

Análisis: si el número en la i-ésima posición es i, significa que no falta ningún número entre 0 y i, y el número que falta puede comenzar desde i+1 hasta el final; si el número en la i-ésima posición no es i, debe ser i-1. Debido a que falta un número entre 0~i, debe continuar buscando 0~i.

Nota: Esta matriz representa una función monótona no decreciente, en la que solo la posición que falta no aumenta, y las otras posiciones son estrictamente crecientes, por lo que es posible la búsqueda binaria.

    def missingNumber(self, nums: List[int]) -> int:
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = int((left + right) / 2)
            if nums[mid] == mid:
                left = mid + 1
            elif nums[mid] > mid:
                right = mid - 1
        return left

LC-793 K ceros después de la función factorial

f(x) es el número de x con 0 al final. Recuerda que x! = 1 * 2 * 3 * ... * x, y 0! = 1.
Por ejemplo, f(3) = 0 porque 3! = 6 no tiene ceros al final y f(11) = 2 porque 11! =39916800 tiene dos ceros al final. Dado k, encuentra el número de enteros no negativos x que devuelven f(x) = k.

Análisis:
Con un número como variable independiente, la función para encontrar el número de 0s después del factorial del número debe ser una función monótona no decreciente, para encontrar el número de x que cumple la condición es encontrar el índice de la menor x que cumple la condición y el mayor El índice de x (dividir para encontrar los límites izquierdo y derecho); otro problema es determinar los límites superior e inferior de la búsqueda. El título se da 0 <= k <= 10 9 , por lo que se estima que x es menor que 10 10.

def preimageSizeFZF(self, k: int) -> int: 
    def get_zero_count(x): # f(x) = get_zero_count(x)是单调非减函数
        res = 0
        inc = int(x/5)
        while inc > 0:
            inc = int(x/5)
            res += inc
            x = inc
        return res

    long_max = 2 ** 64
    # 找左边界:
    l_b = -1
    left = 0
    right = long_max
    while left <= right:
        mid = int((left + right) /2)
        c = get_zero_count(mid)
        if c == k:
            right = mid - 1
        elif c > k:
            right = mid  -1
        elif c< k :
            left = mid + 1

    if left > long_max:
        return 0
    else:
        if get_zero_count(left) == k:
            l_b = left
        else:
            return 0
    # 找右边界:
    r_b = -1
    left = 0
    right = long_max
    while left <= right:
        mid = int((left + right) /2)
        c = get_zero_count(mid)
        if c == k:
            left = mid + 1
        elif c > k:
            right = mid  -1
        elif c< k :
            left = mid + 1
    if right < 0:
        return 0
    else:
        if get_zero_count(right) == k:
            r_b = right
        else:
            return 0
    return r_b - l_b + 1

LC-1011 Capacidad para entregar paquetes dentro de D días

Los paquetes en la cinta transportadora deben entregarse de un puerto a otro en días días.
El i-ésimo paquete en el transportador tiene pesos[i]. Cada día, cargamos la cinta transportadora con paquetes en el orden de pesos dados. No cargaremos más del peso máximo de carga del barco.
Devuelve la capacidad mínima del barco que puede entregar todos los paquetes en la cinta transportadora en días días.

Análisis:
La capacidad de carga máxima c del buque es una variable independiente, y la función de calcular el número de días a transportar bajo esta capacidad de carga es una función monótona no creciente (por supuesto, c debe ser mayor o igual a el valor máximo en pesos); esta pregunta encuentra el número de días dado La c más pequeña es el problema de encontrar el límite izquierdo de la búsqueda binaria; tenga en cuenta que la función no creciente es algo diferente de la función no decreciente anterior cuando se divide en dos; además, los límites superior e inferior de la búsqueda son el valor máximo de la matriz ~ la suma de los valores de la matriz.

def shipWithinDays(self, weights, days: int) -> int: # 1011

    def capacity_to_days(c): # capacity_to_days(x) 是一个非增函数(当x>=weights.max时)
        day_count = 0
        day_remaining_capacity = c
        for w in weights:
            # if c < w:
            #     return -1 # max capacity < any weight
            if w <= day_remaining_capacity:
                day_remaining_capacity -= w
            else:
                day_count += 1
                day_remaining_capacity = c - w
        if day_remaining_capacity < c:
            day_count += 1
        return day_count

    search_start = 0
    search_end = 0

    for w in weights:
        search_start = max(search_start, w)
        search_end += w

    left = search_start
    right = search_end

    while left <= right: # find left bound
        mid = int( (left+right)/2)
        mid_val = capacity_to_days(mid)
        if mid_val == days:
            right = mid - 1
        elif mid_val < days:
            right = mid - 1
        elif mid_val > days:
            left = mid + 1

    # if right < search_start or left > search_end:
    #     return
    return left

LC-875 Keke que le encanta comer plátanos

A Keke le gusta comer plátanos. Hay n montones de bananas, y hay montones[i] de bananas en la i-ésima pila. El guardia ya se ha ido y volverá en h horas.
Keke puede decidir la velocidad k con la que come plátanos (unidad: raíz/hora). Cada hora, elegirá un racimo de plátanos y comerá k plátanos de ellos. Si la pila tiene menos de k bananas, se comerá todas las bananas de la pila y no comerá más bananas durante una hora.
A Ke Ke le gusta comer despacio, pero todavía quiere comerse todos los plátanos antes de que regresen los guardias.
Devuelve la velocidad mínima k (k es un número entero) a la que puede comerse todos los plátanos en h horas.

Análisis:
La velocidad de comer s es una variable independiente, y el tiempo para comer todos los plátanos es una función no creciente; el tiempo mínimo dado en esta pregunta es el problema de búsqueda binaria para encontrar el límite izquierdo; tenga en cuenta que el tiempo no creciente función creciente y la anterior no decreciente Hay algunas diferencias cuando la función se divide en dos, además, los límites superior e inferior de la búsqueda son desde el valor máximo de la matriz hasta la suma de los valores de la matriz.

def minEatingSpeed(self, piles, h: int) -> int: # 875

    def hours_to_eat_by_speed(s): # hours_to_eat_by_speed(x) 是一个非增函数
        return sum([math.ceil(p/s) for p in piles])

    left = 1
    right = sum(piles)

    while left <= right:
        mid = int((left+right)/2)
        mid_val = hours_to_eat_by_speed(mid)
        if mid_val == h:
            right = mid -1
        elif mid_val < h:
            right = mid - 1
        elif mid_val > h:
            left = mid + 1

    return left

LC-410 Valor máximo de matrices delimitadas

Dada una matriz nums de enteros no negativos y un entero m, debe dividir esta matriz en m subarreglos contiguos no vacíos.
Diseñe un algoritmo para minimizar el valor máximo de las respectivas sumas de los m subarreglos.

Análisis:
De hecho, para convertirlo, esta pregunta es la misma que la del problema del buque de carga LC-1011, pero es abstracta; subarreglo = buque de carga, suma de subarreglos = volumen de carga, suma máxima de subarreglos = capacidad del buque de carga, número de subarreglos = número de días de envío.

def splitArray(self, nums, m: int) -> int:

    def split_count_by_max_sum(ms):
        current_array_capacity = ms
        split_count = 0
        for num in nums:
            if num <= current_array_capacity:
                current_array_capacity -= num
            else:
                split_count += 1
                current_array_capacity = ms - num
        if current_array_capacity< ms:
            split_count += 1
        return split_count

    max_element = 0
    nums_total = 0
    for n in nums:
        max_element = max(max_element, n)
        nums_total += n

    left = max_element
    right = nums_total
    while left <= right:
        mid = int( (left+right)/2)
        mid_val = split_count_by_max_sum(mid)
        if mid_val == m:
            right = mid - 1
        elif mid_val < m:
            right = mid - 1
        elif mid_val > m:
            left = mid + 1

    return left

Búsqueda binaria más generalizada

De hecho, la búsqueda binaria se puede usar siempre que cierta información se pueda usar para reducir o reducir a la mitad el rango de respuestas de búsqueda; la función monotónica mencionada anteriormente es solo un caso especial.

Índice de picos LC-852 para matriz de montañas

Una matriz arr que cumple con las siguientes propiedades se denomina matriz de montaña:

arr.length >= 3

Existe i (0 < i < arr.length - 1) tal que:

arr[0] < arr[1] < ... arr[i-1] < arr[i] 
arr[i] > arr[i+1] > ... > arr[arr.length - 1]

Dada su matriz arr de montañas de enteros, devuelva cualquier índice i arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] que .

Análisis:
Si el valor en el índice i es mayor que el valor en i+1, significa que el conjunto montañoso está en la cima o cuesta abajo, es decir, el pico está en [0,i]; si el valor en el índice i es menor que i+1 El valor de , lo que indica que la cadena montañosa está en la parte de arriba, es decir, el pico de la montaña está en [i+1, fin].

def peakIndexInMountainArray(self, arr) -> int:
    left = 0
    right = len(arr) - 1
    while left < right:
        mid = int((left + right)/ 2)
        if arr[mid] - arr[mid+1] < 0:
            left = mid + 1
        else:
            right = mid
    return left

LC-33 Búsqueda de arreglos ordenados rotados

Los números de la matriz de enteros se organizan en orden ascendente, y los valores de la matriz son diferentes entre sí.
Antes de pasar a la función, nums se gira en algún subíndice k predesconocido (0 <= k < nums.length), de modo que la matriz se convierte en [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](subíndice contando desde 0). Por ejemplo, [0,1,2,4,4,4,5,6,6,7]después de la rotación en el subíndice 5 podría convertirse en [4,5,6,6,7,0,1,2,4,4].
Dados los números de su matriz rotada y un objetivo entero, escriba una función para determinar si el valor objetivo dado existe en la matriz. Devuelve verdadero si el objetivo del valor objetivo existe en números; de lo contrario, devuelve falso.

Análisis:
aunque la matriz ya no es monótona después de la rotación, aún es posible juzgar si el número que se busca está en la mitad izquierda o derecha del intervalo según la comparación entre el valor medio de cualquier intervalo y el principio y el final. valores;

  • Si el valor R del lado derecho del intervalo es mayor o igual que el valor medio M, significa que el punto de rotación está en el medio intervalo izquierdo, es decir, el medio intervalo derecho es monótono y el mínimo el valor del intervalo de la mitad derecha es M y el valor máximo es R. Si el número que está buscando está en el rango [M ,R], busque la mitad del área derecha; de lo contrario, busque la mitad del área izquierda;
  • Si L es menor o igual que M, significa que el punto de rotación está en el semiintervalo derecho, es decir, el semiintervalo izquierdo es monótono, y el valor mínimo del semiintervalo izquierdo es L y el valor máximo es M. Si el número que está buscando está en el rango de [L, M] , busque la mitad del área izquierda; de lo contrario, busque la mitad del área derecha;
def search(self, nums, target: int) -> int: 
    left = 0
    right = len(nums) - 1 # [left, right)
    while left <= right:
        mid = int((left+right)/2)
        mid_elem = nums[mid]
        if mid_elem == target:
            return mid
        elif mid_elem >= nums[0]: # left is mono
            if nums[0] <= target < mid_elem:
                right = mid - 1
            else:
                left = mid + 1
        elif mid_elem <= nums[-1]: # right is mono
            if mid_elem < target <= nums[-1]:
                left = mid + 1
            else:
                right = mid - 1

    return -1

Supongo que te gusta

Origin blog.csdn.net/O_1CxH/article/details/126854732
Recomendado
Clasificación