返回

二分查找的左右边界在面试中的运用

前端

二分查找算法的灵活运用:左右边界的巧妙查找

在程序设计的世界里,二分查找算法以其快速高效而闻名。它是一种适用于有序数组的搜索技术,通过不断将搜索区间对半分,从而快速定位目标值。然而,二分查找的应用并不止于此,在实际问题中,它的灵活运用可以衍生出更复杂的解法,尤其是左右边界的查找,在编程面试中尤为常见。

查找第一个等于目标值的元素

想象你手头有一个排列有序的数组,你想要找到第一个等于给定目标值的元素。该如何实现呢?传统的二分查找算法可能会让你陷入困境,因为它只会告诉你目标值是否存在于数组中,但无法精确地找到它的位置。

这时,你需要的是一种改进的二分查找策略,它能专注于查找第一个等于目标值的元素。方法如下:

  1. 初始化搜索区间 :将数组的左右边界分别设为 left 和 right,初始值为 0 和数组长度减一。
  2. 迭代二分查找 :进入二分查找循环,每次迭代都计算出中间索引 mid。
  3. 比较中间元素 :检查 arr[mid] 是否大于等于目标值。
  4. 更新搜索区间 :如果 arr[mid] 大于等于目标值,将 right 更新为 mid,因为目标值可能在左半区间;否则,将 left 更新为 mid + 1,因为目标值肯定在右半区间。
  5. 重复步骤 2-4 :重复执行步骤 2-4,直到 left 大于等于 right。
  6. 检查结果 :如果 arr[left] 等于目标值,则返回 left 作为第一个等于目标值的元素的下标;否则,返回 -1 表示没有找到。

代码示例:

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

查找最后一个等于目标值的元素

与查找第一个等于目标值的元素类似,我们还可以修改二分查找算法来查找最后一个等于目标值的元素。思路如下:

  1. 初始化搜索区间 :和之前相同,将数组的左右边界分别设为 left 和 right,初始值为 0 和数组长度减一。
  2. 迭代二分查找 :进入二分查找循环,每次迭代都计算出中间索引 mid。
  3. 比较中间元素 :检查 arr[mid] 是否小于等于目标值。
  4. 更新搜索区间 :如果 arr[mid] 小于等于目标值,将 left 更新为 mid + 1,因为目标值肯定在右半区间;否则,将 right 更新为 mid,因为目标值可能在左半区间。
  5. 重复步骤 2-4 :重复执行步骤 2-4,直到 left 大于等于 right。
  6. 检查结果 :如果 arr[right] 等于目标值,则返回 right 作为最后一个等于目标值的元素的下标;否则,返回 -1 表示没有找到。

代码示例:

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

应用实例:在有序数组中查找元素的第一个和最后一个位置

LeetCode 34 题要求我们在一个非递减排序的整数数组中找到目标值出现的第一个和最后一个位置。利用我们上面介绍的二分查找左右边界技巧,可以轻松解决此题:

  1. 调用二分查找函数 :对于目标值,调用 find_first_equal() 函数找到第一个等于目标值的元素的下标。
  2. 再次调用二分查找函数 :对于同一个目标值,调用 find_last_equal() 函数找到最后一个等于目标值的元素的下标。
  3. 返回结果 :将这两个下标组成列表返回,[第一个位置,最后一个位置]。

代码示例:

def search_range(nums, target):
    first_index = find_first_equal(nums, target)
    last_index = find_last_equal(nums, target)
    return [first_index, last_index]

常见问题解答

  1. 为什么二分查找不能直接找到第一个或最后一个等于目标值的元素?

    • 因为传统的二分查找算法只返回目标值是否存在,但不知道它的具体位置。
  2. 二分查找左右边界技巧的时间复杂度是多少?

    • 仍然是 O(log n),与传统的二分查找算法相同。
  3. 这些技巧适用于哪些语言?

    • 这些技巧适用于大多数支持数组和二分查找的编程语言,例如 Python、Java、C++ 等。
  4. 在实际应用中,二分查找左右边界技巧有哪些场景?

    • 除了本文中介绍的例子外,这些技巧还可用于查找区间、统计元素出现次数以及其他需要在有序数据中进行精确定位的任务。
  5. 如何优化这些二分查找技巧?

    • 可以通过使用循环不变式和边界检查优化代码的清晰度和效率。