二分查找边界

26 阅读2分钟

二分查找边界

查找左边界

问:给定一个长度为 的有序数组 nums ,其中可能包含重复元素。请返回数组中最左一个元素 target 的索引。若数组中不包含该元素,则返回 -1。

用二分查找插入点的方法,搜索完成后i指向最左一个target,因此查找插入点本质上是在查找最左一个target的索引

数组中可能不包含 target ,这种情况可能导致以下两种结果

  • 插入点的索引i越界
  • 元素nums[i]与target不相等

如果遇到上述情况,可直接返回-1

/* 二分查找最左一个 target */
func binarySearchLeftEdge(nums []int, target int) int {
    //等价于查找target的插入点
    i := binarySearchInsertionSimple(nums, target)
    //未找到target返回-1
    if i == len(nums) || nums[i] != target {
        return -1
    }
    //找到target
    return i
}
​
/* 二分查找插入点(无重复元素) */
func binarySearchInsertionSimple(nums []int, target int) int {
    // 初始化双闭区间 [0, n-1]
    i, j := 0, len(nums)-1
    for i <= j {
        // 计算中点索引 m
        m := i + (j-i)/2
        if nums[m] < target {
            // target 在区间 [m+1, j] 中
            i = m + 1
        } else if nums[m] > target {
            // target 在区间 [i, m-1] 中
            j = m - 1
        } else {
            // 找到 target ,返回插入点 m
            return m
        }
    }
    // 未找到 target ,返回插入点 i
    return i
}

查找右边界

那么如何查找最右一个 target 呢?最直接的方式是修改代码,替换在 nums[m] == target 情况下的指针收缩操作。

我有一种更简单的方法,就是复用查找左边界

  • 将查找最右一个 target 转化为查找最左一个 target + 1

查找完成后,指针i指向最左一个 target + 1(如果存在),而j指向最右一个 target因此返回 即可

/* 二分查找最右一个 target */
func binarySearchRightEdge(nums []int, target int) int {
    // 转化为查找最左一个 target + 1
    i := binarySearchInsertion(nums, target+1)
    // j 指向最右一个 target ,i 指向首个大于 target 的元素
    j := i - 1
    // 未找到 target ,返回 -1
    if j == -1 || nums[j] != target {
        return -1
    }
    // 找到 target ,返回索引 j
    return j
}