||在 Java 中,二分法(Binary Search)是一种高效的查找算法,通常用于在有序数据集(如数组或列表)中快速定位目标元素。它的核心思想是将搜索范围不断缩小一半,从而达到 O(log n) 的时间复杂度。根据应用场景和实现方式,二分法可以分为几种常见的分类。以下是二分法的分类及其对应的例子。
public class BinarySearch {
public static int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 未找到
}
public static void main(String[] args) {
int[] nums = {2, 3, 4, 10, 40};
int target = 10;
int result = binarySearch(nums, target);
System.out.println("目标索引: " + result); // 输出 3
}
}
public class FindFirstAndLast {
// 查找第一个出现的位置
public static int findFirst(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
result = mid; // 记录可能的结果
right = mid - 1; // 继续向左寻找
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
// 查找最后一个出现的位置
public static int findLast(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
result = mid; // 记录可能的结果
left = mid + 1; // 继续向右寻找
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
public static void main(String[] args) {
int[] nums = {5, 7, 7, 8, 8, 10};
int target = 8;
System.out.println("第一个位置: " + findFirst(nums, target)); // 输出 3
System.out.println("最后一个位置: " + findLast(nums, target)); // 输出 4
}
}
public class SearchInsertPosition {
public static int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left; // left 是插入位置
}
public static void main(String[] args) {
int[] nums = {1, 3, 5, 6};
int target = 2;
System.out.println("插入位置: " + searchInsert(nums, target)); // 输出 1
}
}
public class FindPeakElement {
public static int findPeakElement(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[mid + 1]) { // 峰值在左侧(包括 mid)
right = mid;
} else { // 峰值在右侧
left = mid + 1;
}
}
return left; // left 是峰值索引
}
public static void main(String[] args) {
int[] nums = {1, 2, 3, 1};
System.out.println("峰值索引: " + findPeakElement(nums)); // 输出 2
}
}
public class SearchInRotatedSortedArray {
public static int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
}
// 判断哪一半是有序的
if (nums[left] <= nums[mid]) { // 左半部分有序
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else { // 右半部分有序
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1; // 未找到
}
public static void main(String[] args) {
int[] nums = {4, 5, 6, 7, 0, 1, 2};
int target = 0;
System.out.println("目标索引: " + search(nums, target)); // 输出 4
}
}
类型 | 特点 | 典型问题 |
---|---|---|
标准二分查找 | 查找具体值 | 基本查找 |
查找第一个/最后一个 | 处理重复元素边界 | 查找重复元素的范围 |
查找插入位置 | 返回目标应插入位置 | 插入位置问题 |
基于条件的二分查找 | 根据条件划分范围 | 找峰值、最优解 |
旋转数组二分查找 | 处理旋转后的有序数组 | 旋转数组中的查找 |
二分法的关键在于利用数据的有序性或单调性,通过不断缩小搜索范围来提高效率。以上例子展示了二分法在不同场景下的应用。如果需要更深入的解释或更多例子,请告诉我!
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right)
还是 while(left <= right)
,到底是right = middle
呢,还是要right = middle - 1
呢?
大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
迭代、递归 和 Java 中的 Arrays.binarySearch。这三种方式都可以实现二分查找的功能,只是实现方式和使用场景有所不同。以下我会详细讲解这三种形式,并为每种形式提供具体的 Java 示例。
while
循环)来不断缩小搜索范围,直到找到目标或确定目标不存在。public class IterativeBinarySearch {
public static int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免溢出
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 未找到
}
public static void main(String[] args) {
int[] nums = {2, 3, 4, 10, 40};
int target = 10;
int result = binarySearch(nums, target);
System.out.println("目标索引: " + result); // 输出 3
}
}
public class RecursiveBinarySearch {
public static int binarySearch(int[] nums, int target, int left, int right) {
if (left > right) {
return -1; // 未找到
}
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
return binarySearch(nums, target, mid + 1, right); // 递归右半部分
} else {
return binarySearch(nums, target, left, mid - 1); // 递归左半部分
}
}
public static void main(String[] args) {
int[] nums = {2, 3, 4, 10, 40};
int target = 10;
int result = binarySearch(nums, target, 0, nums.length - 1);
System.out.println("目标索引: " + result); // 输出 3
}
}
Arrays.binarySearch
,适用于有序数组。它返回目标值的索引,如果未找到则返回负值(具体为 -(插入点) - 1
)。import java.util.Arrays;
public class ArraysBinarySearch {
public static void main(String[] args) {
int[] nums = {2, 3, 4, 10, 40};
int target = 10;
int result = Arrays.binarySearch(nums, target);
if (result >= 0) {
System.out.println("目标索引: " + result); // 输出 3
} else {
System.out.println("未找到,插入点: " + (-result - 1)); // 如果未找到
}
// 测试未找到的情况
target = 5;
result = Arrays.binarySearch(nums, target);
System.out.println("未找到,插入点: " + (-result - 1)); // 输出 3
}
}
表示形式 | 实现方式 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
迭代 | 循环 | O(1) | 高效,节省空间 | 代码稍显冗长 |
递归 | 递归调用 | O(log n) | 逻辑清晰,易读 | 栈空间开销 |
Arrays.binarySearch | 内置方法 | O(1) | 简单方便 | 灵活性低 |
Arrays.sort()
)。left <= right
(而不是 <
),以确保不会遗漏边界元素。left + (right - left) / 2
而不是 (left + right) / 2
,以防止整数溢出。希望这些示例和说明能帮你更好地理解二分法的三种表示形式!