二分法
二分查找算法的基本思想
在一个有序序列中,取中值与目标值做对比,从而选取左区间之一,不断重复取值选区间这个过程,直到找到值或者达到边界条件而退出循环。
由基本思想可以的到二分的两个关键点:
- 左右区间的选取(包不包含中值)-- 闭区间还是开区间 [left,mid]/[left,mid)/(mid,right]/[mid,right]
- 边界条件的选取 (l<=r或l<r)
- l<=r 的情况下,循环中的最后一步l==r,此时中值索引是l®,中值与target作比较导致 l+1(退出条件1)或者r-1(退出条件2),从而r<l 退出循环。ps: 此时若关键点1是闭区间包含中值,则有可能导致死循环。
- l<r的情况下, 最后剩下rl+1, 中值与target做比较 ,由2个推出条件导致 lr 退出循环。
1.查找精确值
在一个有序序列中查找符合要求的精确值。查找值为target的元素下标,不存在则返回-1。
两个关键点的选取:
- 左右区间选取时,已判断当前中值不是目标值,故排除中值,是开区间。选取[left,mid)或(mid,right]。
- 边界两个都可以
// 这里用的是l<=r,
// 最后 当l=r时,mid=l,
// nums[mid]>target, r=mid-1,r<l 退出循环
// nums[mid]<target, l = mid+1,l>r 退出循环
// 若用 l<r, 最后一步,当nums[mid]!=target时 l或r移动,l=r,退出循环。 此时的nums[l]是最后答案或是大于target的第一个元素。
public static int binarySearch(int[] nums, int l, int r, int target){
while(l<=r){
int mid = l + (r-l)/2;
if(nums[mid] == target){
return mid; //找到目标值,提前退出循环,返回值
}else if(nums[mid]>target){
//右区间
r = mid-1; //mid不可能为答案,去掉mid。
}else{
//左区间
l = mid+1; //mid不可能为答案,去掉mid。
}
}
return -1;
}
2.左边界(找索引位置)
(1,1,2,2,2,3)这样一个序列找2的最左位置的索引,没有则返回-1。
两个关键点:
-
当中值等于target时,因为不确定这个值是否为最左值,排除这个中值,右区间开区间[left,mid),当中值小于或大于target时,也是开区间选取 (mid,right]或[left,mid)。
-
由于1选的都是开区间最后 r是位于最左位置的左边,所以用l<=r循环最后一步r==l,用l+1退出循环刚好就是答案
// 找值为target的处于最左位置的索引 public int leftBound(int[] nums,int l, int r, int target){ while(l<=r){ int mid = l + (r-l)/2; if(nums[mid]>=target){ r = mid-1; //开区间 }else{ l = mid+1;//开区间 } } // 越界或target不存在 if(l>=nums.length || nums[l] != target) return -1; return l; }
本题的关键点:
当中值遇到target的时候,继续取开区间[left,mid)把中值排除,如果排除到(最左位置-1)就锁定了最左边界
。不断向左收缩锁定最左边界。
3.右边界(找索引位置)
(1,2,2,2,3,3)这样一个序列找2的最右位置的索引,没有则返回-1。
与左边界同理,不断向右收缩锁定最右边界。
public int leftBound(int[] nums,int l, int r, int target){
while(l<=r){
int mid = l + (r-l)/2;
if(nums[mid]>target){
r = mid-1; //开区间
}else{
l = mid+1;//开区间
}
}
// 越界或trarget不存在
if(r<0 || nums[r] != target)
return -1;
return r;
}
4.找大于等于或大于 target的第一个元素(找值)
两个关键点:
-
当中值满足题中条件但不确定是否为第一个元素时,没法排除,取闭区间。不满足则可开区间排除
-
选取 l<r 时, 最后是r==l,r只在满足条件下移动,l只做排除区间的行为,故 l(r)就是答案.
选取l<=r是,由于不确定l,r谁是第一个,判断大小即可。
public int findNum1(int[] nums, int l, int r, int target){
while(l < r){
int mid = (r + l) / 2;
if(nums[mid] > target){ // 查找大于时不加=,大于等于时 nums[mid] >= target
r = mid; //因为找的时大于target的第一个值,nums[mid]右边全排除。
}else{
l = mid+1;
// [left,mid]全部排除
}
}
return nums[r];
}
5.查找小于等于/小于target的最后一个元素(找值)
与4同理
public int findNum1(int[] nums, int l, int r, int target){
while(l < r){
int mid = (r + l) / 2;
if(nums[mid] < target){ // 查找小于时不加=,小于等于时 nums[mid] <= target
l = mid; //因为找的时大于target的第一个值,nums[mid]右边全排除。
}else{
r = mid+1;
// [left,mid]全部排除
}
}
return nums[r];
}