二分查找进阶:查找最靠左和最靠右的索引(Java实现)

一、引言

在实际开发中,二分查找(Binary Search)是一种高效的查找算法,尤其在处理有序数组时表现出色。然而,标准的二分查找只能返回目标值的任意一个位置(例如中间位置)。如果需要找到目标值的最左索引最右索引(例如统计重复元素的出现次数),或者只需要单纯知道最左或最有


二、普通二分查找 vs. 边界查找

1. 普通二分查找

public static int binarySearch(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
        else if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

特点:当目标值存在多个时,返回的是任意一个位置(通常是中间的)。


2. 查找最靠左的索引(左边界)

核心思想
  • 当 arr[mid] == target 时,不立即返回,而是将搜索区间向左收缩(right = mid - 1),继续寻找更小的索引。
  • 最终,left 会收敛到最左的位置,但需要通过变量保存候选值。
代码实现
public static int binarySearchLeftmost(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    int candidate = -1; // 保存候选位置
    while (left <= right) {
        int mid = (left + right) >>> 1; // 无符号右移,等价于除以2
        if (arr[mid] == target) {
            candidate = mid; // 记录当前索引
            right = mid - 1; // 向左收缩
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return candidate;
}
逻辑解析
  1. 初始化left 和 right 定义搜索区间 [left, right]
  2. 循环条件while (left <= right) 确保所有元素都被检查。
  3. mid 计算:使用 (left + right) >>> 1 避免整数溢出(虽然 Java 中 int 的范围足够大)。
  4. 关键操作
    • 当 arr[mid] == target 时,记录当前索引 candidate = mid,但继续向左搜索(right = mid - 1)。
    • 循环结束后,candidate 保存的是最左的索引。
示例
int[] arr = {1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10};
System.out.println(binarySearchLeftmost(arr, 7)); // 输出 6

3. 查找最靠右的索引(右边界)

核心思想
  • 当 arr[mid] == target 时,不立即返回,而是将搜索区间向右收缩(left = mid + 1),继续寻找更大的索引。
  • 最终,right 会收敛到最右的位置,但需要通过变量保存候选值。
代码实现
public static int binarySearchRightmost(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    int candidate = -1; // 保存候选位置
    while (left <= right) {
        int mid = (left + right) >>> 1;
        if (arr[mid] == target) {
            candidate = mid; // 记录当前索引
            left = mid + 1; // 向右收缩
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return candidate;
}
逻辑解析
  1. 初始化:与左边界查找相同,定义搜索区间 [left, right]
  2. 关键操作
    • 当 arr[mid] == target 时,记录当前索引 candidate = mid,但继续向右搜索(left = mid + 1)。
    • 循环结束后,candidate 保存的是最右的索引。
示例
int[] arr = {1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10};
System.out.println(binarySearchRightmost(arr, 7)); // 输出 7

三、代码对比与注意事项

功能 左边界查找 右边界查找
搜索方向 向左收缩(right = mid - 1 向右收缩(left = mid + 1
候选值更新 candidate = mid candidate = mid
最终返回值 candidate candidate
适用场景 统计重复元素的起始位置 统计重复元素的结束位置

注意事项

  1. mid 计算(left + right) >>> 1 是无符号右移,等价于 (left + right) / 2,但在 Java 中更推荐使用 left + (right - left) / 2 以避免潜在的整数溢出问题。
  2. 边界检查:如果目标值不存在,candidate 返回 -1。可以通过添加额外判断增强健壮性。

四、应用场景

  1. 统计重复元素的出现次数

    int left = binarySearchLeftmost(arr, target);
    int right = binarySearchRightmost(arr, target);
    int count = (left == -1) ? 0 : (right - left + 1);
  2. 查找第一个大于等于目标值的元素
    修改比较逻辑即可实现。

  3. 解决 LeetCode 题目

    • 34. 在排序数组中查找元素的第一个和最后一个位置

五、总结

  • 标准二分查找:适用于查找任意一个目标值。
  • 左边界查找:通过 right = mid - 1 缩小范围,最终返回最左索引。
  • 右边界查找:通过 left = mid + 1 缩小范围,最终返回最右索引。
  • 关键点:在 arr[mid] == target 时,不要立即返回,而是调整搜索区间继续搜索。

你可能感兴趣的:(算法学习,算法,java,intellij-idea)