第一个想到的方法肯定是暴力破解法
class Solution {
public int searchInsert(int[] nums, int target) {
for(int i =0;i < nums.length;i++){
if(nums[i]>= target){
return i;
}
}
return nums.length;
}
}
第二个方法对于排序数组,立马想到了二分查找
那么聊一聊二分查找
传统的二分查找
取中位数的索引
int mid = (left + right)/2
其实这行代码是有问题的,在left和right都比较大的时候,left+right很有可能超过int类型能表示的最大值,整数溢出
int mid = left + (right - left)/2
更好的写法是:
int mid = (left+right)>>>1;
循环的条件是,while(left <= right)时,再退出循环的时候,需要考虑返回left还是right
当把二分查找法的循环可以进行的条件写成 while (left <= right) 时,在写最后一句 return 的时候,如果不假思索,把左边界 left 返回回去,虽然写对了,但可以思考一下为什么不返回右边界 right 呢?
2、但是事实上,返回 left 是有一定道理的,如果题目换一种问法,你可能就要返回右边界 right,这句话不太理解没有关系,我也不打算讲得很清楚(在上面代码的注释中我已经解释了原因),因为实在太绕了,这不是我要说的重点。
由此,我认为“传统二分查找法模板”使用的痛点在于:
传统二分查找法模板,当退出 while 循环的时候,在返回左边界还是右边界这个问题上,比较容易出错。
那么,是不是可以回避这个问题呢?答案是肯定的,答案就在下面我要介绍的“神奇的”二分查找法模板里。
关于力扣看到的二分查找法模板的基本思想
首先把循环可以进行的条件写成 while(left < right),在退出循环的时候,一定有 left == right 成立,此时返回 left 或者 right 都可以
或许你会问:退出循环的时候还有一个数没有看啊(退出循环之前索引 left 或 索引 right 上的值)?
没有关系,我们就等到退出循环以后来看,甚至经过分析,有时都不用看,就能确定它是目标数值。
(什么时候需要看最后剩下的那个数,什么时候不需要,会在第 5 点介绍。)
更深层次的思想是“夹逼法”或者称为“排除法”。
“神奇的”二分查找法模板的基本思想(特别重要)
“排除法”即:在每一轮循环中排除一半以上的元素,于是在对数级别的时间复杂度内,就可以把区间“夹逼” 只剩下 1 个数,而这个数是不是我们要找的数,单独做一次判断就可以了。
“夹逼法”或者“排除法”是二分查找算法的基本思想,“二分”是手段,在目标元素不确定的情况下,“二分” 也是“最大熵原理”告诉我们的选择。
还是 LeetCode 第 35 题,下面给出使用 while (left < right) 模板写法的 2 段参考代码,以下代码的细节部分在后文中会讲到,因此一些地方不太明白没有关系,暂时跳过即可。
(right - left) 不加 11 选左中位数,加 11 选右中位数。
class Solution {
public int searchInsert(int[] nums, int target) {
int len = nums.length;
if(nums[len-1]< target){
return len;
}
int left = 0;
int right = len -1;
while(left <= right){
int mid =(right + left)/2;
//如果等于,直接返回
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid -1;
}
}
return left;
}
}