目录
一、寻找一个数(基本的二分搜索)
二、寻找左侧边界的二分搜索
写法一
写法二
相关题目
参考文章
int binary_search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}
1、为什么 while 循环的条件中是 <=,而不是 <?
答:因为初始化right
的赋值是nums.length - 1
,即最后一个元素的索引,而不是nums.length
。
这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间[left, right]
,后者相当于左闭右开区间[left, right)
,因为索引大小为nums.length
是越界的。
2、为什么left = mid + 1
,right = mid - 1
?我看有的代码是right = mid
或者left = mid
,没有这些加加减减,到底怎么回事,怎么判断?
答:这也是二分查找的一个难点,不过只要你能理解前面的内容,就能够很容易判断。
刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即[left, right]
。那么当我们发现索引mid
不是要找的target
时,下一步应该去搜索哪里呢?
当然是去搜索[left, mid-1]
或者[mid+1, right]
对不对?因为mid
已经搜索过,应该从搜索区间中去除。
3、为什么返回left
而不是right
?
答:都是一样的,因为 while 终止的条件是left == right
。
比如我们给定数组1,2,3,4,4,4,5,6,7,7,8,9,我们需要查找第一个等于4的元素,「搜索区间」是[left, right]
因为我们初始化 right = nums.length-1,所以决定了我们的「搜索区间」是 [left, right],所以决定了 while (left <=right)。决定了下一步的二分区间为[left,mid-1] [mid+1,right]
同时也决定了 left = mid +1 和 right = mid-1
因为我们需找到 target 的最左侧索引,所以当 nums[mid] == target 时不要立即返回,而要令 right = mid - 1锁定左侧边界
int left_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 最后要检查 left 越界的情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
因为我们初始化 right = nums.length,所以决定了我们的「搜索区间」是 [left, right),所以决定了 while (left < right),决定了下一步的二分区间为[left,mid) [mid+1,right) ,同时也决定了right = mid 和 left = mid +1
因为我们需找到 target 的最左侧索引,所以当 nums[mid] == target 时不要立即返回,而要令 right = mid 锁定左侧边界
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
return left;
}
注意:第一次搜索到的nums[mid]==target的值可能是最左边,中间,最右边的数据。对于寻找左右边界的left和right的赋值的逻辑是不一样的。
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
public int[] searchRange(int[] nums, int target) {
// 分别寻找左边界和右边界
int leftIndex = findBound(nums, target, true);
int rightIndex = findBound(nums, target, false);
// 如果左边界超过了右边界,说明没有找到目标值
if (leftIndex > rightIndex) {
return new int[]{-1, -1};
}
return new int[]{leftIndex, rightIndex};
}
private int findBound(int[] nums, int target, boolean findLeft) {
int left = 0, right = nums.length - 1, ans = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
ans = mid; // 记录找到的位置
// 如果寻找左边界,向左继续查找;如果寻找右边界,向右继续查找
if (findLeft) {
right = mid - 1;
} else {
left = mid + 1;
}
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans;
}
}
35. 搜索插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
// 使用二分查找法
while (left <= right) { // 应该是 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; // 调整右边界
}
}
//如果目标值不存在于数组中,那么最终 left 指针将指向第一个大于目标值的元素位置,right 指针将指向最后一个小于目标值的元素位置。这是因为
//如果目标值小于数组中的所有元素,循环结束时 left 为 0。
//如果目标值大于数组中的所有元素,循环结束时 left 为 nums.length。
//如果目标值位于数组中的两个元素之间,循环结束时 left 指向的是第一个大于目标值的元素位置。
//因此,无论目标值是否存在于数组中,循环结束时 left 指针总是指向目标值应该被插入的位置
return left;
}
}
算法刷题入门数据结构|二分查找