• 二分查找的各种写法以及对应边界

    搜索区间为闭区间[i,j]
    关于二分查找 - 图1的取值,需要覆盖整个搜索区间。
    关于返回值,根据实际情况确定。
    比如,在精准查找某个元素时,我们可以写成如下的代码:

    1. def search(nums, target):
    2. l, r = 0, len(nums) - 1
    3. while l <= r:
    4. mid = (l + r) // 2
    5. if nums[mid] == target:
    6. return mid
    7. elif nums[mid] < target:
    8. l = mid + 1
    9. else:
    10. r = mid - 1
    11. return -1

    而如果是在一个没有重复元素的数组里查询目标值,查询不到返回顺序插入的位置,代码则应该写成

    1. class Solution:
    2. def searchInsert(self, nums: List[int], target: int) -> int:
    3. l , r = 0, len(nums) - 1
    4. while l <= r:
    5. mid = (l + r) // 2
    6. if nums[mid] == target:
    7. return mid
    8. elif nums[mid] < target:
    9. l = mid + 1
    10. else:
    11. r = mid - 1
    12. return l

    搜索区间为开区间[i,j)
    两种更新方式

    mid=(l+r) // 2 mid=(l+r+1) // 2
    l = mid + 1 l = mid
    r = mid r = mid - 1

    可以参考基础练习的leftBound和rightBound两个函数的实现来加深理解。这样的更新方式是为了防止死循环。

    • python库函数的使用

    导入:from bisect import *
    参数形式:有序列表,目标值

    bisect_left 返回在有序数组中插入目标值的位置,如果数组中存在相同值,返回最左侧下标。
    bisect_right(同bisect) 返回在有序数组中插入目标值的位置,如果数组中存在相同值,返回最右侧下标。
    insort_left 在有序数组中插入目标值,如果数组中存在相同值,最左侧插入。
    insort_right(同insort) 在有序数组中插入目标值,如果数组中存在相同值,最右侧插入。
    • 基础练习
      • 返回在有序数组中插入目标值的位置,如果数组中存在相同值,返回最左侧下标。(对应python中bisect库的bisect_left)

    返回值的范围在二分查找 - 图2二分查找 - 图3表示数组的长度。返回0时表示,插入的元素比最小值还小,或者相等。返回二分查找 - 图4时表示插入的元素比最大值还大。

    1. def bisect_left(nums, target): # 自己的实现
    2. l, r = 0, len(nums)
    3. while l < r:
    4. mid = (l + r) // 2
    5. if nums[mid] < target:
    6. l = mid + 1
    7. else:
    8. r = mid
    9. return l
    • 返回在有序数组中插入目标值的位置,如果数组中存在相同值,返回最右侧下标。(对应python中bisect库的bisect_right)

    返回值的范围在二分查找 - 图5二分查找 - 图6表示数组的长度。返回0时表示,插入的元素比最小值还小,返回二分查找 - 图7时表示插入的元素比最大值还大或者相等。

    1. def bisect_right(nums, target):
    2. l, r = 0, len(nums)
    3. while l < r:
    4. mid = (l + r) // 2
    5. if nums[mid] <= target:
    6. l = mid + 1
    7. else:
    8. r = mid
    9. return l
    • 返回在有序数组中目标元素第一次出现的位置。不存在则返回-1。循环结束后还需要对边界值进行特殊判断。

      1. def leftBound(nums, target):
      2. l, r = 0, len(nums) - 1
      3. while l < r:
      4. mid = (l + r) // 2
      5. if nums[mid] < target:
      6. l = mid + 1
      7. else:
      8. r = mid
      9. return l if nums[l] == target else -1
    • 返回在有序数组中目标元素最后一次出现的位置。不存在则返回-1。循环结束后还需要对边界值进行特殊判断。

      def rightBound(nums, target):
      l, r = 0, len(nums) - 1
      while l < r:
         mid = (l + r + 1) // 2
         if nums[mid] <= target:
             l = mid 
         else:
             r = mid - 1
      return l if nums[l] == target else -1
      
    • 利用二分查找搜索边界点

    我们称一个分割整数数组的方案是 好的 ,当它满足:
    数组被分成三个 非空 连续子数组,从左至右分别命名为 left , mid , right 。
    left 中元素和小于等于 mid 中元素和,mid 中元素和小于等于 right 中元素和。
    给你一个 非负 整数数组 nums ,请你返回 好的 分割 nums 方案数目。由于答案可能会很大,请你将结果对二分查找 - 图8取余后返回。
    题目来源:LeetCode
    题目链接:https://leetcode-cn.com/problems/ways-to-split-array-into-three-subarrays
    数据规模:
    3 <= nums.length <= 10
    0 <= nums[i] <= 10
    分析:
    数据规模决定了采用时间复杂度为二分查找 - 图9的算法。
    首先需要构造前缀和数组,前缀和数组定义为二分查找 - 图10。因为数组中元素均为非负数,所以前缀和数组不严格单增。
    设两个切分点为二分查找 - 图11,满足二分查找 - 图12
    所以思路是枚举所有的二分查找 - 图13,寻找满足条件的二分查找 - 图14并且计数。
    固定二分查找 - 图15,寻找第一个满足条件的二分查找 - 图16,使得二分查找 - 图17
    移项,寻找最后一个满足条件的二分查找 - 图18,使得二分查找 - 图19
    参考解答:

    class Solution:
        def waysToSplit(self, nums: List[int]) -> int:
            mod = 10**9 + 7
            n = len(nums)
            pre = list(accumulate(nums))  # 前缀和数组,不含有前导0 from itertools import accumulate
            ans = 0
            for i in range(n):
                l = max(i+1, bisect_left(pre, pre[i]*2)) # 找到大于等于目标值的下标,相同元素返回最左下标
                r = min(n-1, bisect_right(pre, (pre[i]+pre[-1])//2)) # 若寻找到的数恰好等于目标值,返回最右下标;若大于目标值,返回当前下标
                ans += max(0, r - l) # j的可行区间为[l, r)
            return ans % mod  #  python可以最后再取余