解题思路
经典的二分法查找,通过题目解析可知查找的元素位置为:查找中间值大于等于目标值的第一个元素索引
参考[1]
//java
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 = (left + right) / 2;
// 等于的情况最简单,我们应该放在第 1 个分支进行判断
if (nums[mid] == target)
{
return mid;
}
else if (nums[mid] < target)
{
// 题目要我们返回大于或者等于目标值的第 1 个数的索引
// 此时 mid 一定不是所求的左边界,
// 此时左边界更新为 mid + 1
left = mid + 1;
}
else
{
// 既然不会等于,此时 nums[mid] > target
// mid 也一定不是所求的右边界
// 此时右边界更新为 mid - 1
right = mid - 1;
}
}
// 注意:一定得返回左边界 left,
// 如果返回右边界 right 提交代码不会通过
// 【注意】下面我尝试说明一下理由,如果你不太理解下面我说的,那是我表达的问题
// 但我建议你不要纠结这个问题,因为我将要介绍的二分查找法模板,可以避免对返回 left 和 right 的讨论
// 理由是对于 [1,3,5,6],target = 2,返回大于等于 target 的第 1 个数的索引,此时应该返回 1
// 在上面的 while (left <= right) 退出循环以后,right < left,right = 0 ,left = 1
// 根据题意应该返回 left,
// 如果题目要求你返回小于等于 target 的所有数里最大的那个索引值,应该返回 right
return left;
}
}
算法复杂度分析:
时间复杂度:O(logN) 原因是数组长度N每次划分为一半,2^x=N 求解x可得出
空间复杂度:O(1)
使用二分法模板:
二分查找的改进以及相关的题型
把if (nums[mid] == target) 的语句省略,首先第一步判断目标值是否能包含在左右边界之中,找出左右边界。若不能包含在左右边界之中,最后通过二分法排除只剩下的最后一个元素(夹逼法)还要做最后一次判断,以判断目标值是否和最后剩下的值相等。
第二步是 依据题目写出中位数排除的判断分支 left=mid+1
第三步是 对应的中位数不能排除的分支 right=mid
或者第二步和第三步颠倒过来
第二步 right=mid-1 第三步 left=mid
第四步返回left或者right即可。
注意偶数时:左中位数和右中位数的区分选择(通过最后剩下两个元素时判断)。以避免死循环
//java
class Solution
{
public int searchInsert(int[] nums, int target)
{
int len = nums.length;
if (len == 0)
{
return -1;
}
if (nums[len - 1] < target)
{
return len;
}
int left = 0;
int right = len - 1;
//排除上述特殊情况后,依据题目可以确定目标值一定在在左右边界之中
while (left < right)
{
int mid = left + (right - left) / 2;
if (nums[mid] < target) //依据题目排除中位数(此判断中位数小于目标值,而题目要找的是大于或等于目标值的第一个元素)
{
// nums[mid] 的值可以舍弃
left = mid + 1;
}
else //中位数大于或等于目标值
{
// nums[mid] 不能舍弃
right = mid;
}
}
//循环结束只剩下最后一个值
return right;
}
}
总结该模板和普通二分法的差别在于:
1)去掉了中间值的判断等于,新的模板在最后会得到最后一个数
2)在该模板中需要确定要找的元素是否包含在边界内(left right)若不在则最后得到的元素要再做一次判断
3)新模板最后返回的left=right
4)排除中位数的写法有两种,两种写法要注意的问题是:左中位数和右中位数的选择不同的写法不同,注意不然容易溢出
参考资料