算法刷题-基础篇
二分查找/插入
1.基本思路
二分查找主要运用于有序数组(至少要有一定规律),通过每次舍去区间中不满足要求的一部分从而达到O(logn)的时间复杂度
2.例子
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。(你可以认为数组严格递增)
请必须使用时间复杂度为 O(log n) 的算法。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-insert-position
3.分析
二分法就是去除数据中不符合要求的一半,从而达到时间复杂度为 O(log n) 的目的,因而一般二分是针对有序数据设置left和right两个边界点,并以mid=(left+rigth)/2此作为是否舍弃的依据,属于思路简单但是编写多变的算法。
实际上,二分法问题在编写的时候面临的主要问题有两个:
1、mid不符合条件之后,left和right如何变化?
答:在代码中一般以0作为初始的left,而right依据实际情况而定,由于在代码中float转int是直接截断,这就导致问题转化为mid是否需要保留,由mid 的计算方式可以得出一定条件下mid会等于left(left+1==right)。因而为了保障程序不会死循环,一般是left=mid+1或者right=mid-1,另一半保持不变作为下一次循环的初始条件。
简而言之,当mid不符合条件,你需要舍弃哪一部分的数据。
2、中止条件是left>right还是left>=right?
这个主要是查看当left与right的时候是否需要继续,一般与你的二分查找的返回值有关。需要注意的是当left>right作为退出的时候left最多等于right,
left>=right时left最多等于right+1
4.实际编写
首先要针对具体问题具体分析,此题要求的是找到严格小于target的最后一个数字,最后返回其索引加一。我们需要明确的有下列条件
a.数组严格递增
b.需要的是最后一个小于target的数据的索引+1
为了不失一般性,选择mid作为返回值。我们不妨以left>right作为退出条件,思考极限情况,则循环退出时left=right但是mid不等于他们两个,故最终返回值如下
return left==right?left: mid;
此时二分法的思路就很明确了,当nums【mid】=target 直接break即可
当nums【mid】<target时我们需要舍弃掉【left,mid】这一部分的数据。这很好理解,我们需要mid往上增加作为最终的返回值,思考极限情况
【1,3】作为nums,2作为target,初始left=0,right=1,则mid=0,而我们所需要的结果为1,因而需要left=mid+1作为此时跳转条件
if(nums[mid]<target)
{
left=mid+1;
}
同理,当nums【mid】>target,我们需要舍弃【mid+1,right】这一部分数据,因而需要令right=mid
else if(nums[mid]>target)
{
right=mid;
}
最后,我们可以通过令right=nums.length来避免进行一次额外判定,最后完整java代码如下:
class Solution {
public int searchInsert(int[] nums, int target) {
int left=0;
int right=nums.length;
int mid=0;
while(left<right)
{
mid=(left+right)/2;
if(nums[mid]<target)
{
left=mid+1;
}
else if(nums[mid]>target)
{
right=mid;
}
else
{
break;
}
}
return left==right?left: mid;
}
}
复杂度分析:
时间复杂度:O(logN),这里 N是输入数组的长度;
空间复杂度:O(1)。
5.拓展
最后,不妨思考如果退出条件为left<=right的时候相应的思考过程是怎么样的?