「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 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
如果是暴力解法,直接从小到大遍历,找到合适位置就好。但是这需要依次遍历,这样时间复杂度为O(n),超过了O(logn)。而O(logn),很明显需要使用二分法,每次各取长度的一半。因为目标只有四种情况
- 在数组所有元素之前
- 在数组所有元素之后
- 与数组某些元素相同
- 插入数组某两个位置中间
而二分法的逻辑很简单,难的是寻找二分法的边界。
例如到底是 while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1呢?
这里主要是对区间的定义没有想清楚,对边界和不变量的了解不够。
以这道题为例,这是一个左右闭合的区间。[left,right]
二分法第一种写法
var searchInsert = function (nums, target) {
let n = nums.length;
let left = 0;
let right = nums.length - 1; // left ,right定义了左右区间
while (left <= right) { // 当left== right时,区间[left,right]依旧有效
let mid = left + Math.floor((right - left) / 2)
if (nums[mid] > target) {
right = mid - 1 // target在左区间,[left,mid-1]
} else if (nums[mid] < target) {
left = mid + 1 // target在右区间,[left+1,right]
} else { // 数组中找到target
return mid
}
}
// 插入值在所有元素之前,[0,-1] 此时right = -1 , 插入点为right+1=0
// 目标所在值所有元素之后,[0,right+1] , return right+1
// 目标等于某个元素,return mid
// 目标在数组中某个位置 [left,right],此时left<=right,还会循环两次,right-1,left+1,所以,return right+1
return right + 1
};
二分法第二种方法
如果target是在一个左闭右开的区间里,也就是[left,right)
那么二分法的处理方法则不同。
这个[left,right)是永远不会变的,所以,如何循环,都是左闭右开
var searchInsert = function (nums, target) {
let n = nums.length;
let left = 0;
let right = n; // 定义target在左闭右开的区间里,[left, right) target
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间
let mid = left + Math.floor((right - left) / 2)
if (nums[mid] > target) {
right = mid // target 在左区间,在[left, mid)中
} else if (nums[mid] < target) {
left = mid + 1 // target 在右区间,在 [mid+1, right)中
} else {
return mid // 数组中找到目标值的情况,直接返回下标
}
}
// 插入值在所有元素之前 [0,0) 此时right = 0 , return right
// 目标所在值所有元素之后 return mid
// 目标等于某个元素 [left, right) ,return right 即可
// 目标在数组中某个位置 [left,right),此时left<right,还会循环一次,right-1,所以,return right
return right
};
只要确定是[]还是[),以后一直坚持这个闭合空间就好。