前序
二分法是很重要并且非常基础的查找算法,为什么很多同学对于它都是,一看就会,一写就费,接下来,花上几分钟,把这几个小细节抓透。
区间的定义
在开始手撕二分法之前,我们来谈一谈区间的问题。区间的左端点,右端点的情况,一共有三种:左闭右闭 [ ],左闭右开[ ),还有左开右闭[ )。
了解了这三种区间端点的定义后,我们就从这些定义中,用一个例子,直截了当地一眼写出while语句的判断条件。 因为左闭右开的区间可以两个端点值都取到,所以它的右边就可以相当于“<=”的作用,而左闭右开,或者左开右闭的区间,它的“(”或者“)”就相当于“>”或者“<”对应的端点值。下面用一段简单的二分查找代码来进一步让这个概念更加清晰。
int search(int* nums, int numsSize, int target){
int left = 0;
int right = numsSize-1;
int middle = 0;
while(left<=right) {
middle = (left+right)/2;
if(nums[middle] > target) {
right = middle-1;
}
else if(nums[middle] < target) {
left = middle+1;
}
else if(nums[middle] == target){
return middle;
}
}
return -1;
}
主函数中定义一个search方法,传入一个数组(升序),数组长度,和目标数值,注意! 此时我们定义的right为数组长度(numsSizs)-1,此时target是在左闭右闭[left,right]的区间中,当left == right时是有意义的,所以while循环的判断条件要用“<=”。
当我们要进入下一次if中时,我们可以断定当前的nums[middle]的下标middle必然不是我们要查找的值的下标,所以当下一次while时,就可以不用将这时的下标middle加入判断中。if判断nums[middle] > target时,将下一次进入while循环中的right = middle-1;,nums[middle] < target时,left=middle-1,因为是左边的后一位,右边的前一位,最后找到nums[middle] == target,返回 middle目标值target的下标,未找到则返回-1。
我们来看有开区间的情况
int search(int* nums, int numsSize, int target){
int left = 0;
int right = numsSize;
int middle = 0;
while(left < right){
int middle = left + (right - left) / 2;
if(nums[middle] < target){
left = middle + 1;
}else if(nums[middle] > target){
right = middle ;
}else{
return middle;
}
}
return -1;
}
这时的right = numsSize,此时target在[left,right)区间中,当left == right时,根据区间的定义,右边端点是取不到的,此时while的判断条件就为“<”。if的判断条件是一样的。
左开右闭的情况在大型项目中不常见,在平时的算法题目中也不多,它的原理和[left,right)是相同的,这里就不再赘述,让干货最干。