文章目录
以下假设数组递增。
0、普通二分
目标:从数组中寻找target,有则返回下标,没有则返回-1。
返回值范围:[-1, n-1]
终止while条件:如果不包含target,即正常退出while,则此时必有left=right+1。此时区间[left,right]不包含任何元素,返回-1。
区间缩小规则:
- 如果mid==target,返回mid。
- 如果mid>target,在[left, mid-1]找,
- 否则,在[mid+1,right]找。
特点:如果有多个元素=target,会返回任意一个。
public static int binarySearch(int[] arr, int target){
int left = 0;
int right = arr.length-1;
while(left<=right){// 注意
int mid = (left+right)/2;
if(arr[mid]==target){
return mid;
}else if(arr[mid] >target){
right = mid-1;
}else if(arr[mid]<target){
left = mid+1;
}
}
return -1;
}
为什么right初始值是arr.length-1,终止while前有left=right,进而mid=right,如果right=arr.length,会越界。而且根据返回值的意义和范围,left和right需要设置成这样。
1、找第一个大于等于target的下标
目标:找第一个大于等于target的下标;
返回值还有一个含义:小于target的数有几个。
具体来说,如果数组里面有target,返回第一次等于target的元素下标:如果不包含target,则返回第一个大于target 的元素下标。若所有元素均小于target,返回n。
返回值范围:[0,n]
终止while条件:必有left==right。此时区间[left,right]里只有一个元素,直接返回left或right
区间缩小方法:
- 如果mid元素大于等于target。要找的必定是当前位置或者当前位置左边,令right=mid,即在[left,mid]之间找。
- 如果mid元素小于target,要找的下标必定在右边:left = mid+1,即在[mid+1, right]之间找。
// 只有4个需要注意的地方。
public static int binarySearch(int arr[], int target){
int left = 0;
int right = arr.length; // 注意
while(left<right){ // 注意
int mid = (left+right)/2;
if(arr[mid] >= target){// 注意
right = mid;
}else{
left = mid+1;
}
}
return left;// 注意
}
为什么right初始值是arr.length。因为终止while前必有left<right,进而mid<right,所以不用担心越界。而且根据返回值的意义和范围,left和right需要设置成这样。
2、找第一个大于target的下标
目标:找第一个大于target的下标;没有大于target的元素则返回n
返回值还有一个含义:小于等于target的数有几个。
返回值范围:[0, n]
终止while条件:必有left==right。此时区间[left,right]里只有一个元素,直接返回left或right
区间缩小方法:
- 如果mid元素大于target。要找的必定是当前位置或当前位置左边,right=mid,即在[left,mid]之间找。
- 如果mid元素小于等于target,要找的下标必定在右边:left = mid+1,即在[mid+1, right]之间找。
public static int binarySearch(int arr[], int target){
int left = 0;
int right = arr.length;
while(left<right){
int mid = (left+right)/2;
if(arr[mid] > target){// 和上面的代码唯一区别是这里
right = mid;
}else{
left = mid+1;
}
}
return left;
}
3、找最后一个小于等于target的下标
作用:找出最后一个小于等于target的元素下标。所有元素均大于target则返回-1.
返回值范围:[-1,n-1]
分析:用上面的方法可以找第一个大于target的下标index,因此index-1对应的元素必定不大于target,index-1即为所求。
所以只需要把上面的代码最后一行改成left-1就可以了。
public static int binarySearch(int arr[], int target){
int left = 0;
int right = arr.length;
while(left<right){
int mid = (left+right)/2;
if(arr[mid] > target){
right = mid;
}else{
left = mid+1;
}
}
return left-1;// 注意,和上面代码唯一区别在这里
}
A、找target第一次/最后一次出现的位置
上面可以找出第一个大于等于target的下标index。如果index对应元素等于target,则index则为所求。否则说明不包含target。
同理,上面可以找出最后一个小于等于target的下标index。如果index对应元素等于target,则index则为所求。否则说明不包含target。
注:很多人说除了普通二分,其他while里面是left<right,由此说明其搜索区间是左闭右开,但是我觉得仍然应该理解成左闭右闭。
B、java自带二分法库函数
集合
int Collections.binarySearch(List, target)
从有序集合中搜索target:
- 找到则返回下标。(如果有多个target,返回任意一个)
- 没找到则返回一个负数index,-index-1 表示比target小的数的数量,即第一个大于target的下标。即target插入数组应该放在哪个位置。
例如在数组 [3,6,6,6,7,10]中找6,返回2;找5,返回-2.说明5应该插在第一个元素。
顺便一提collections集合的主要方法:
sort
reverse
shuffle
fill
swap 交换list中的两个元素
copy(dest, src)
min
max
add addAll
remove removeAll
retainAll
rotate 所有元素右移一段距离
replaceAll 修改oldval 为 newval
indexOfSubList
数组
int Arrays.binarySearch(array, target)
效果同上。
C、总结
例子:
原数组: {3,6,6,9,10,11} 长度n=6
方法 | 返回值范围 | target=0的返回值 | target=6的返回值 | target=15的返回值 |
---|---|---|---|---|
普通二分 | [-1,n-1] | -1 | 1/2 | -1 |
第一个大于等于target | [0,n] | 0 | 1 | 6 |
第一个大于target | [0,n] | 0 | 3 | 6 |
最后一个小于等于target | [-1,n-1] | -1 | 2 | 5 |
自带库函数binarySearch | [-n-1,n-1] | -1 | 1/2 | -7 |
1/2表示可能是1也可能是2.