二分模板(左右查询)

复制代码
public class BinarySearch {
    public static int Binarysearch(int[] a, int target) {
        int i = 0 , j = a.length - 1; //设置指针和初值
        while (i <= j) { //范围内有东西
            int m = (i + j) >>> 1; // i~j;
            if(target < a[m]) { // 目标在左边
                j = m - 1;
            } else if(a[m] < target) { //目标在右边
                i = m + 1;
            } else {
                return m; //找到
            }
        }
        return -1;
    }
}
 /*
   1  i<=j 而不是 i<j 的原因
      i j 它们指向的元素也会参与比较
   2  (i + j) / 2 为什么不这么写
      Java  同一个二进制数
          10111_11111_11111_11111_11111_11110
          不把最高位视为符号位,代表 3221225470
          把最高位视为符号位,代表 - 1073741826
   3  为什么都写成小于号
         代码可读性高,
 */

在 Java 中,二分查找(Binary Search)的 "左查询" 和 "右查询" 通常用于在有序数组中精确查找目标元素的第一个出现位置 (左查询)或最后一个出现位置(右查询)。这两种查询在处理存在重复元素的数组时非常有用。

核心场景

当数组中存在重复元素时,普通二分查找可能返回任意一个匹配元素的位置,而左 / 右查询可以精准定位到重复元素的边界:

  • 左查询:找到目标元素第一次出现的索引(最左侧)。
  • 右查询:找到目标元素最后一次出现的索引(最右侧)。

实现逻辑(基于有序数组)

假设数组是升序排列的,核心思路是通过调整二分查找的边界收缩条件,控制最终定位到左边界或右边界。

1. 左查询(找第一个出现的位置)

目标 :在数组中找到目标值 target 第一次出现的索引,若不存在则返回 -1。

关键逻辑 :当中间元素 nums[mid] == target 时,不立即返回,而是继续向左收缩右边界(right = mid - 1),确保找到更左侧的匹配元素。

复制代码
public static int leftBound(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;
    int result = -1; // 初始化为-1(默认不存在)

    while (left <= right) {
        int mid = (right + left)>>>1;
        if (nums[mid] == target) {
            result = mid; // 记录当前位置(可能是左边界)
            right = mid - 1; // 继续向左找,缩小右边界
        } else if (nums[mid] < target) {
            left = mid + 1; // 目标在右侧,缩小左边界
        } else {
            right = mid - 1; // 目标在左侧,缩小右边界
        }
    }
    return result;
}
2. 右查询(找最后一个出现的位置)

目标 :在数组中找到目标值 target 最后一次出现的索引,若不存在则返回 -1。

关键逻辑 :当中间元素 nums[mid] == target 时,不立即返回,而是继续向右收缩左边界(left = mid + 1),确保找到更右侧的匹配元素。

复制代码
public static int rightBound(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;
    int result = -1; // 初始化为-1(默认不存在)

    while (left <= right) {
        int mid = (right + left)>>>1;

        if (nums[mid] == target) {
            result = mid; // 记录当前位置(可能是右边界)
            left = mid + 1; // 继续向右找,缩小左边界
        } else if (nums[mid] < target) {
            left = mid + 1; // 目标在右侧,缩小左边界
        } else {
            right = mid - 1; // 目标在左侧,缩小右边界
        }
    }
    return result;
}

实际操作

复制代码
import java.util.Scanner;
public class Main{
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n=sc.nextInt();
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) {
            nums[i] = sc.nextInt();
        }
        int target = scanner.nextInt();
        // 3. 调用左查询和右查询方法
        int leftIndex = leftBound(nums, target);
        int rightIndex = rightBound(nums, target);

        // 4. 输出结果
      System.out.println("leftIndex: " + leftIndex + ",rightIndex: " + rightIndex);
    }

    // 左查询:找第一个出现的位置
    private static int leftBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int result = -1;
        while (left <= right) {
            int mid = (right + left)>>>1; // 避免溢出
            if (nums[mid] == target) {
                result = mid; // 记录当前位置,继续向左找
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1; // 目标在右侧
            } else {
                right = mid - 1; // 目标在左侧
            }
        }
        return result;
    }

    // 右查询:找最后一个出现的位置
    private static int rightBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int result = -1;
        while (left <= right) {
            int mid = (right + left)>>>1;
            if (nums[mid] == target) {
                result = mid; // 记录当前位置,继续向右找
                left = mid + 1;
            } else if (nums[mid] < target) {
                left = mid + 1; // 目标在右侧
            } else {
                right = mid - 1; // 目标在左侧
            }
        }
        return result;
    }
}
相关推荐
天河归来8 小时前
第三方接口不稳定,如何让核心任务继续完成
java·大数据
Knight_AL8 小时前
Java 文件下载时文件名中的 `#` 被转换成 `%23`?一次 URL 编码与解码问题排查
java·开发语言
ZhengEnCi8 小时前
GPT-6 Astra 爆改 Qwen 权重真有那么神吗:权重空间编程 0.016% 参数的真相
算法·架构
罗西的思考9 小时前
【轻量化复刻大模型 / KIMI K3】KDA(Kimi Delta Attention):并行
人工智能·算法·机器学习
砚底藏山河9 小时前
python量化自定义周期 K 线合成
java·开发语言·数据库·python·金融
猿究院_夏燊10 小时前
MySQL 的四种隔离级别
java·数据库·mysql
码流子10 小时前
移动收费怎么做?手持终端+App+收费中心的架构实践
java·开发语言·架构
霸道流氓气质11 小时前
AI 流式对话与实时聊天平台:SSE 解码竞态、有状态会话隔离与背压取消的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
夜不会漫长11 小时前
C++:红黑树
数据结构·算法
云创智城-yuncitys11 小时前
YunParking 4.0:JDK 17 与 Spring Boot 3.2 的停车云升级
java·开发语言·spring boot