• 二分查找


    基本知识

    当数据量很大适宜采用二分查找方法。
    采用二分法查找时,一般来说数据需是排好序的. 其实二分查找的核心是单调, 这个数组有序这是单调的一种情况, 对于局部调单也可以使用二分查找

    基本思想:假设数据是按升序排序的,对于给定值key,从序列的中间位置k开始比较,
    如果arr[k]=key,则查找成功
    key < arr[k],则在数列的前半段中查找 (arr[low,mid-1])
    key > arr[k],则在数列的后半段中继续查找 (arr[mid+1,high])
    根据以上步骤, 我们可以得到其时间复杂度为O(log(n))

    mid的确定位置, 有三种写法

    1. mid = (l + r) / 2 , 单l+r时,可能会因为数太大了而溢出
    2. mid = l + (r - l) / 2, 不会溢出
    3. mid = 1 + ((r - l) >> 2), 2法使用右移一位, 比除2快

    跳出循环的条件
    这是二分查找的难点!!
    一般来说, 跳出循环的条件有两个: low < highlow <= high

    按照条件有下图的情况:
    边界的情况
    根据这些情况, 需要根据问题, 进行具体分析
    一般来说, 要返回mid的话, 边界条件为low < high, 否则为low <= high, 当然一些问题仍然需要自己根据题目的要求进行分析

    算法题

    有序数组中找到num

    问题: 在一个有序数组中, 找某个数是否存在
    这是最简单的二分法的算法题了

    from typing import List
    
    
    def find_num(lst: List[int], num: int) -> bool:
        low = 0
        high = len(lst) - 1
        while low <= high:
            mid = low + (high - low) >> 1
            if num > lst[mid]:
                low = mid + 1
            elif num < lst[mid]:
                high = mid - 1
            else:
                return True
        return False
    
    
    print(find_num([1, 2, 3, 4], 0))
    print(find_num([1, 2, 3, 4], 1))
    
    

    有序数组中找到>=num最左的位置

    问题: 在一个有序数组中, 找>=某个数的最左的位置
    如: [1, 1, 2, 2, 2, 2, 2, 2, 3]中大于等于2的数字的最左位置

    题解

    判断[1, 1, 2, 2, 2, 2, 2, 2, 3]
    low = 0 high = 8mid为4
    由于lst[3] = 4 < 2, 所以high = mid - 1 = 3即 从[1, 1, 2, 2]中继续判断
    low = 0 high = 3mid为1
    由于lst[1] = 1 > 2, 所以low = mid + 1 = 2, 即 从 [2, 2]中继续判断
    low = 2 high = 3mid为2
    由于lst[2] = 2 >= 2, 所以high = mid - 1 = 1, 由于 low > high, 跳出循环
    返回low, 即右边的那个数

    代码

    from typing import List
    
    
    def find_left(lst: List, num: int) -> int:
        low = 0
        high = len(lst) - 1
    
        while low <= high:
            mid = int((low + high) / 2)
    
            if lst[mid] >= num:
                # >= num 时满足条件
                # 但不知道前面是否还满足, 所以需要继续判断
                high = mid - 1
            else:
                # 不满足条件
                # 修改左区间
                low = mid + 1
        return low
    
    
    print(find_left([1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4], 3))  # 8
    print(find_left([1, 1, 2, 2, 2, 2, 2, 2, 3], 2))  # 2
    
    

    有序数组中找到<=num最右的位置

    问题: 在一个有序数组中, 找<=某个数的最右的位置
    同样与上面的问题一样, 只不过把判断条件换了
    代码:

    from typing import List
    
    
    def find_right(lst: List[int], num: int) -> int:
        low = 0
        high = len(lst) - 1
    
        while low <= high:
            mid = int((low + high) / 2)
            if lst[mid] <= num:
                low = mid + 1
            else:
                high = mid - 1
        return high
    
    
    print(find_right([1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4], 3))  # 17
    print(find_right([1, 1, 2, 2, 2, 2, 2, 2, 3], 2))  # 7
    
    

    局部最小值问题

    什么是局部最小值?

    1. arr[0] < arr[1],0位置是局部最小
    2. arr[N-1] < arr[N-2],N-1位置是局部最小
    3. arr[i-1] > arr[i] < arr[i+1],i位置是局部最小

    问题

    给定一个数组arr,已知任何两个相邻的数都不相等,找到随便一个局部最小位置返回
    在Leetcode上有一道类似的题目, 不过求的是局部最大值, 见: 山脉数组的峰顶索引, 其题解见: 山脉数组的峰顶索引的题解

    题解

    一般来说, 解题的方向有两个:

    1. 数据状况
    2. 问题标准

    这个问题相邻两个数不相等, 且求的是局部最小值, 所以可以用二分查找的方式

    第一步: 比较0位置和1位置的值, 判断0位置的值是否为局部最小的值, 是则返回
    第二步: 比较N-1位置和N-2位置的值, 判断N-1位置的值是否为局部最小的值, 是则返回

    假如不是0和N-1位置均不是局部最小值, 那么 1 到 N-2 范围内无论如何必然存在局部最小值, 如下图表示:
    示意图

    假如使用二分查找的方法, 每次去中间的值mid
    判断mid是否为局部最小值, 是则返回
    否则, 比mid小的一边必然存在局部最小值, 以此类推, 如下图:
    示意图

    代码

    from typing import List
    
    
    def find_min(lst: List[int]) -> int:
        """
        寻找局部最小值
        """
        lst_len = len(lst)
        if lst_len <= 1 or lst[0] < lst[1]:
            return 0
    
        if lst[lst_len - 1] < lst[lst_len - 2]:
            return lst_len - 1
    
        # 正式处理中间
        mid = 0
        low = 0
        high = lst_len - 1
        while low < high:
            mid = low + (high - low) // 2
            if lst[mid - 1] > lst[mid] and lst[mid] < lst[mid + 1]:
                return mid
    
            # 取比当前数小的那边
            if lst[mid - 1] < lst[mid]:
                high = mid
            else:
                low = mid
        return mid
    
    
    print(find_min([1, 2, 7, 8, 9]))  # 0
    print(find_min([9, 8, 7, 5, 3]))  # 4
    print(find_min([10, 2, 7, 8, 9]))  # 1
    print(find_min([10, 9, 2, 8, 11]))  # 2
    print(find_min([10, 9, 8, 7, 11]))  # 3
    
    print(find_min([1, 2, 7, 8, 9, 10]))  # 0
    print(find_min([9, 8, 7, 5, 3, 2]))  # 5
    print(find_min([10, 2, 7, 8, 9, 11]))  # 1
    print(find_min([10, 9, 2, 8, 11, 12]))  # 2
    print(find_min([10, 9, 8, 7, 11, 13]))  # 3
    print(find_min([10, 9, 8, 7, 4, 13]))  # 4
    
  • 相关阅读:
    [转]VSTO Office二次开发RibbonX代码结构
    [转]VSTO+WinForm+WebService+WCF+WPF示例
    Ext Js简单Data Store创建及使用
    Web页面常用文件格式文件流的输出
    VSTO Office二次开发PPTRibbonX命令操作及对象添加
    VSTO Office二次开发键盘鼠标钩子使用整理
    VSTO Office二次开发对PPT自定义任务窗格测试
    VSTO Office二次开发对PowerPoint功能简单测试
    Ext Js简单Tree创建及其异步加载
    VB获取和更改文件属性
  • 原文地址:https://www.cnblogs.com/lczmx/p/15801709.html
Copyright © 2020-2023  润新知