数据结构课程就有学习到二分法的思想和实现,但自己的动手实践太少,用二分法解题的练习也很少,但直到前段时间真正动手用二分法解题的时候才发现二分法里面的细节很多,边界条件很难把握,用 <
还是<=
,return left
还是 return right
分不清。
二分查找的一些细节:
-
不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。
-
计算 mid 时需要防止溢出,代码中 使用left + (right - left) / 2来代替 (left + right) / 2 ,有效防止了 left 和 right 太大直接相加导致溢出。
-
若初始化
right
的赋值是nums.length - 1
,即最后一个元素的索引,则每次的查找区间相当于 [ left , right ] 即两端都闭区间;若初始化right
的赋值是nums.length
,则查找区间相当于 [ left , right ) 即左闭右开区间。因此在
while
循环的条件处两种情况有所差别,第一种情况应当为**while(left <= right)
,即当left=right+1
时终止,此时查找区间为 [right , right+1] 为空; 第二种情况应当为while(left < right)
**,即当left==right
时终止,此时查找区间为 [ right , right ) 为空。
根据以上几点,就大概可以解决二分查找的相关问题了,特别是第三点的总结让我受益匪浅。以下为总结的二分查找的两种
格式:
int binary_search_case1(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;
}
int binary_search_case2(int[] nums, int target) {
int left = 0, right = nums.length;
while(left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
// 直接返回
return -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;
}