二分法的使用
对于二分法,我们首先要明确一点,就是right究竟取何值。
right = nums.length - 1 or right = nums.length ?
其实这这两种都是二分法的形式,只不过看你如何区分区间。
case1:
left = 0 , right = nums.length
整体的模板形式如下:
while (left < right){
int mid = left + ((right - left)>>1);
if (nums[mid] < target){
left = mid + 1;
} else if (nums[mid] > target){
right = mid - 1;
}else{
return mid;
}
}
我们注意到几个关键点,while的条件是left < right ,为什么不是left <= right呢? 还记得right的初始值是nums.length吗,这个值本身不包含在nums可以取到的下标里,也就是说这其实是应该左闭右开的区间,开区间的值取不到就谈不来讨论等于了。
同时对于 left = mid + 1, 与 right = mid - 1,为什么要这样写,很简单,对于小于target的情况此时的mid已经是小于了,之后讨论的值无需带上它。对于大于target,由于right是开区间,在下一次进入循环时讨论右区间的值是没有意义的,只有mid - 1才有意义。
case 2:
left = 0 , right = nums.length - 1
整体的模板形式如下:
while (left <= right){
int mid = left + ((right - left)>>1);
if (nums[mid] < target){
left = mid + 1;
} else if (nums[mid] > target){
right = mid;
}else{
return mid;
}
}
此时 while的条件就变为了 left <= right , 显而易见的原因是right此时的值是一个可以取到的值,是左闭右闭的区间,当小于target的时候,由于同样的原因,left = mid + 1,而right此时由于可以取到的,所以保持mid即可。
例题1:
简单的二分用法,无需解释
例题2:
上一题的变体,若值不存在,由于是一个顺序数组,nums【mid】最后一定是小于target的,此时left = mid + 1,也就是left++,此时left来到了应该被插入的位置。
例题3:
对于此题,我的思路就是先找出一个值,从这个值往两边扩,直到扩不动。
例题4:
此题,可以直接return两个特殊值,由平方的性质可知,结果一定是在小于x的一半的区间中产生。注意,在判断时 采用 mid * mid <= x 会产生溢出,所以使用除法. 在mid <= (x/mid)的区间内,可能会有值也可能没有值(取等时为答案),我们每一次都将该值赋给结果,二分到最后,只会留下相等的值,即为结果。
例题5:
本题是例题4的变体,由平方的性质可知right取num的一半, 其余直接二分即可