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;
}
}