这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
搜索插入位置(题号35)
题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:
输入: nums = [1,3,5,6], target = 0
输出: 0
示例 5:
输入: nums = [1], target = 0
输出: 0
提示:
1 <= nums.length <= 104-104 <= nums[i] <= 104nums为无重复元素的升序排列数组-104 <= target <= 104
链接
解释
这题啊,这题是简我击(简单题我重拳出击)。
如果不看题目上的强制要求:
请必须使用时间复杂度为
O(log n)的算法。
那么实际操作会非常简单,直接遍历一次就好,如果运气好甚至都不用走完,但这样做的时间复杂度就是O(n)了,并不符合题目的规范,这个o(nlogn)就明显告诉我们使用二分去操作。
其实这个二分只要最简单的二分就好了,每次只需要根据mid来更新left和right的位置,直到left大于right就结束,所以while的循环条件就是left <= right。
说到这就再看看二分的基本原理吧,首先这是一个升序的数组,且没有重复项。
那么mid就是左指针和右指针和的一半,然后用地板除取整数,注意这里不是四舍五入,就是单纯的取整数部分,小数部分不要。这里不要小数部分是有原因的,因为后续赋值的时候会在mid的基础上+1或者-1,如果四舍五入了,就会出现增加两个数字的情况,这是不可取的。
取到mid之后,如果mid比目标值大,那就证明目标值在数组的前半段,将right赋值为mid-1即可;如果mid比目标值小,那证明目标值在数组后半段,此时将left赋值为mid+1即可。为什么要+1呢?因为mid已经被排除在外了,在将mid带入存在区间没有意义,更重要的是因为循环终止条件是left <= right,如果不对mid进行+1或-1操作,left永远不会比right大,那么此时循环永远不会终止,死循环也就产生了。
回到题目本身,为什么最后的left值就是target的位置呢?有两种情况:
-
target存在于数组中循环会一直走到
right+1的那一部分,此时right被赋值给mid-1,right会不断变小,直到right < left,此时的left就会是答案了 -
target不存在于数组中不存在于数组中就简单一些了,只需要定位到一个位置,此时的左指针应该比
target小,右指针应该比target大,那么此时的左指针自然就是target应该插入的位置了
说了这么多,直接看看代码吧
自己的答案(双指针)
var searchInsert = function(nums, target) {
let left = 0, right = nums.length - 1
while (left <= right) {
const mid = ~~((left + right) / 2)
if (nums[mid] < target) {
left = mid + 1
} else {
right = mid - 1
}
}
return left
};
跟上面说的思路一样,一直循环到终止条件的位置,没啥可说的。
官方的解答(双指针)
var searchInsert = function(nums, target) {
const n = nums.length;
let left = 0, right = n - 1, ans = n;
while (left <= right) {
let mid = ((right - left) >> 1) + left;
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
};
官方答案感觉有点多此一举的意思,非要存个ans变量,可能是处于代码理解层面的优化,这里就不得而知了。
PS:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇