二分查找的思想
二分查找也称折半查找,是一种效率较高的查找方法。但是,折半查找要求线性表必须是顺序存储结构,而且表中元素按关键字有序排列。
查找过程:
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。它的时间复杂度为O(log2n)。
示例代码
public static int binarySearch(Integer[] srcArray, int des) {
//定义初始最小、最大索引
int start = 0;
int end = srcArray.length - 1;
//确保不会出现重复查找,越界
while (start <= end) {
//计算出中间索引值
int middle = (end + start)>>>1 ;//防止溢出
if (des == srcArray[middle]) {
return middle;
//判断下限
} else if (des < srcArray[middle]) {
end = middle - 1;
//判断上限
} else {
start = middle + 1;
}
}
//若没有,则返回-1
return -1;
}
相信大家对二分查找已经很熟悉了,这里就不再多说,下面看看几个二分查找的变形题目。
变形题目
排序数组
题目描述:给出一个排序数组,找到目标值返回其索引,找不到返回插入位置。例如:给出数组{2,5,7,8},若目标为5,则返回1,若目标为6,则返回2。
思路解析:这是一道典型的二分查找的变形题目,我们先按照二分查找的思路进行查找,找到返回其下标。如果没有找到,判断此时基准位置和目标元素的大小,如果基准大于目标值,则需要判断此时基准下标位置是否为0,这一步主要是为了防止数组越界。若基准小于目标值,则可以直接返回基准位置下标加1。
代码如下:
public static int search(int[] arr,int target){
int low = 0;
int high = arr.length-1;
int mid = 0;
while (low<=high){
if(arr[mid]>target){
low = mid+1;
}else if(arr[mid]<target){
high = mid-1;
}else return mid;
}
if(arr[mid]>target){ //防止出现数组越界
return mid -1>=0?mid:0;
}else return mid+1;
}
贪吃的小Q
题目描述:小Q的父母要出差N天,走之前给小Q留下了M块巧克力。小Q决定每天吃的巧克力数量不少于前一天吃的一半,但是他又不想在父母回来之前的某一天没有巧克力吃,请问他第一天最多能吃多少块巧克力。
思路解析:因为巧克力是整数,第一天吃的巧克力在1到M之间,我们可以利用二分查找找出第一天吃的数目。这里我们可以假设第一天吃s个,可以计算出总数是多少。然后利用二分查找将基准当作s传入计算所得出的总数是否与M相等,便可以找出第一天最多吃多少巧克力。
代码如下
public static int sum(int s,int n){
int sum = 0;
for (int i=0;i<n;i++){
sum+=s;
s=(s+1)/2;
}
return sum;
}
public static int Q(int N,int M){
if(N==1) return M;
int low = 1;
int high = M;
while (low<=high){
int mid = (low+high+1)/2;
if(sum(mid,N)==M) {
return mid;
}
else if(sum(mid,N)>M){
low = mid+1;
}else if(sum(mid,N)<M){
high = mid-1;
}
}
return high;
}
旋转数组的最小值
题目描述: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路解析:虽然这道题不是完全有序,但是可以堪称两个有序序列,我们还是可以考虑使用二分法。首先,设置低位位置为0,高位为length-1。如果此时低位小于高位,说明数组并未旋转,直接返回低位元素。如果低位位置大于高位,说明最小值肯定在右边数组,则让低位位置加一。如果相等,则高位减一。其他情况则说明此时基准在右边数组,但最小值在基准左边,则让高位等于基准,最终返回低位元素。
代码如下:
public static int min(int[] arr){
int low = 0;
int high = arr.length-1;
while (low<high){
if(arr[low] <arr[high]){
return arr[low];
}
int mid = low+(high-low)/2;
if(arr[mid]>arr[high]){
low = mid+1;
}else if(arr[mid] == arr[high]){
high = high-1;
}else {
high=mid;
}
}
return arr[low];
}