总的来说,二分查找思想是简单的,都知道它是那么一回事。但是一到去实际编程,总会冒出几个bug出来。二分查找的细节真的堪比玄学。
典型的就比如下面这几个细节:
1.整型溢出
2. mid要加一还是减一还是什么也不做;
3. while的判断是left < right 还是left <= rigth;
做为典型小白吗,头疼了这么久,今天还是来彻底解决下。本篇博文主要参考这个力克题解而做的一个笔记总结:
二分查找细节详解,顺便赋诗一首
下面进入正题:
目录
1. 二分查找的基本框架
首先二分查找的框架基本就是这个样子,记住它!
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
2.防止整型溢出
注意观察会发现:
mid = left + (right - left) / 2;
而不直接是:
mid = (left+right)/2
这里是为了防止了 left 和 right 太大直接相加导致溢出。
3.二分法在有序数组中的简单应用
力克链接:二分查找
题目描述
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
代码:
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right) { //注意1
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意2
else if (nums[mid] > target)
right = mid - 1; // 注意2
}
return -1;
}
注意1—left <= rigth
这里while的判断是left <= rigth;而用left<rigth就会出错。
分析:
此算法中每次进行搜索的区间是[left,right];
循环结束的条件有两种情况:
- nums[mid] == target 找到了目标元素
- 搜索区间为空
要搜索区间为空,那当然要求left > right;一般我们写循环时都要仔细思考临界条件。这里我们同样思考下极端情况,
如果二分搜索的达到最后,恰好是 : right =tagert
那么如果当left = right - 1时就退出循环,那么也就无法正确查找啦!
注意2—left = mid + 1,right = mid - 1
-
首先为什么这里要加一,减一?
因为我们上一步已经查找过了nums[mid],所以就可以加一减一啦! -
必须要这样吗?
表面上看,这里如果不加一减一,好像就是多了一个数字,也不会影响最终结果。但是我以亲身的教训发现,这里还真的必须要加一减一,否则就会陷入死循环。
当left = rigth -1时,是不是(left+right) /2就等于left,如果left 直接等于mid,是不是就无限循环下去了!
仔细斟酌斟酌,如果还没懂就自己模拟下面这一组数:
在数组[1,2]中寻找3,你会发现死循环了;
4.二分查找的进阶
上面这个经典的二分查找算法有局限性:
比如有序数组 nums = [1,2,2,2,3],target 为 2,此算法返回的索引是 2。但是如果想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的
力克链接
题目描述:
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
对于这个题我就直接套用了上面的二分查找:
代码如下:
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] ans = new int[2];
if(nums == null || nums.length == 0)
{
ans[0]=ans[1]=-1;
return ans;
}
int left=0,right = nums.length-1;
int index = 0;
int mid = 0,flag =0,len=nums.length;
while(left<=right)
{
mid = left + (right-left) / 2;
if(target < nums[mid])
{
right = mid -1;
}
else if(target > nums[mid])
{
left = mid + 1;
}
else
{
index = mid;
flag = 1;
break;
}
}
if(flag == 1)
{
ans[0] = index;
//向下搜寻它的最小左边界
for( ;ans[0]>=0 && nums[ans[0]] == target ;ans[0]--);
ans[0]++;
//向下搜寻它的最大右边界
ans[1] = index;
for(;ans[1]<len && nums[ans[1]] == target ;ans[1]++);
ans[1]--;
}else{
ans[0]=-1;
ans[1]=-1;
}
return ans;
}
}
当然如果你想不畏困难,直视二分的更高一级细节处理,看下面的链接吧!
https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/
希望以后的二分查找能顺利写出代码来吧 ^ . ^