📜  二进制搜索的变体(1)

📅  最后修改于: 2023-12-03 15:06:20.582000             🧑  作者: Mango

二进制搜索的变体

二进制搜索(binary search)是一种在有序数组中查找特定元素的常用算法。但是有时候需要对二进制搜索进行一些变形,以满足特定的需求。本文将介绍二进制搜索的三种变体:上下界二分、第一个出现和最后一个出现。

上下界二分

在二分查找的过程中,我们只需要判断当前元素是否等于目标元素,如果不等于,则需要将搜索区间缩小一半。但是在某些情况下,我们需要查找和目标元素相等的序列中的全部元素,这时就需要使用上下界二分。

有时候需要查找特定值在数组中的出现范围(比如查找第一个大于某个值的元素或最后一个小于某个值的元素),即查找包括特定值在内的某个数值范围。这时候,需要考虑到查找区间的上下界。具体思路如下:

  • 首先,假设我们要查找目标元素target。
  • 我们使用二分找到第一个等于target的元素的下标 low,如果不存在,返回-1。
  • 我们再使用二分查找找到最后一个等于target的元素的下标 high。
  • 那么,[low, high]就是target在数组中的出现范围。

这里,我们定义了两个辅助函数:分别用于查找上下界。代码如下:

def find_lower_bound(nums: List[int], target: int) -> int:
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return lo

def find_upper_bound(nums: List[int], target: int) -> int:
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] <= target:
            lo = mid + 1
        else:
            hi = mid - 1
    return hi

然后,我们可以使用这两个函数来查找上下界。代码如下:

def search_range(nums: List[int], target: int) -> List[int]:
    if not nums:
        return [-1, -1]
    low = find_lower_bound(nums, target)
    high = find_upper_bound(nums, target)
    if low <= high:
        return [low, high]
    else:
        return [-1, -1]

这里的时间复杂度是 O(logn),空间复杂度是O(1)。

第一个出现和最后一个出现

有时候需要查找某个元素在数组中的第一个出现位置或最后一个出现位置。这时候,可以考虑使用二分查找的一个变体——双指针二分查找。

具体思路:我们在正常的二分查找基础上,增加两个指针left和right,用于查找第一个出现的位置和最后一个出现的位置。具体来说:

  • 用二分查找找到第一个等于target的元素下标mid;
  • 如果mid位置上的元素等于target,那么我们将right指向mid,left指向mid;
  • 接着,我们从[left, mid-1]区间找到第一个等于target的位置left;从[mid+1, right]区间找到最后一个等于target的位置right;
  • 最终,[left, right]就是target在数组中的出现范围。

代码实现如下:

def search_range(nums: List[int], target: int) -> List[int]:
    if not nums:
        return [-1, -1]
    left, right = binary_search(nums, target)
    if left == -1 or right == -1:
        return [-1, -1]
    while left > 0 and nums[left-1] == target:
        left -= 1
    while right < len(nums)-1 and nums[right+1] == target:
        right += 1
    return [left, right]

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

这里的时间复杂度同样是O(logn),空间复杂度是O(1)。