一、二分查找
1.查找有序数组的元素下标
- 示例1
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4 - 示例2
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
1.1 左闭右闭写法
区间的定义这就决定了二分法的代码应该如何写,因为定义target在 [left, right] 区间,所以有如下三点:
- right的初始化值应该取数组长度减一,因为右区间可以取等。
- while (left <= right)要是使用 <= ,因为left == right是有意义的,举例[1,1]。
- 当nums[middle] > target时,right赋值应该为middle - 1,因为当前这个nums[middle]一定不是target。
JS 代码
var BinaryLookup = function(nums,target) {
let left = 0,right = nums.length - 1;
while (left <= right){
let middle = left + ((right - left) >> 1); //(right - left) >> 1 相当于(left + right) / 2
if (nums[middle] > target){
right = middle - 1;
}else if (nums[middle] < target){
left = middle + 1
}else {
return middle;
}
}
return -1;
}
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
1.2 左闭右开写法
如果定义target在 [left, right) 区间,所以有如下三点:
- right的初始化值应该取数组长度,为了让右区间取到数组最后一个值。
- while (left < right)要是使用 < ,因为left == right是有无意义的,举例[1,1]。
- 当nums[middle] > target时,right赋值应该为middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle](比如数组1,2,4,5,6,target = 3,mid = 2,满足判断条件如果right = mid - 1会完美错开下标2这个位置)
GO 代码
func BinaryLookup(nums []int, target int) int {
left, right := 0, len(nums)
for left < right {
middle := left + (right-left)>>1
if nums[middle] > target {
right = middle
} else if nums[middle] < target {
left = middle + 1
} else {
return middle
}
}
return -1
}
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
2.搜索插入位置
- 描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
- 示例 1:
输入: [1,3,5,6], 5
输出: 2 - 示例 2:
输入: [1,3,5,6], 2
输出: 1
2.1 左闭右闭写法
基于上个题目的理论,分别处理如下四种情况:
- 目标值在数组所有元素之前 nums = [2,3,4], target = 1;
left的值为0不会改变,right的值最后参与循环会到-1,返回left或者right+1均可。 - 目标值等于数组中某一个元素 return middle;
这种情况符合第一个题目。 - 目标值插入数组中的位置 [left, right]之间
当最后一次满足nums[left] < target < nums[right], 且right - left = 1;target的下标应该是right才对哦。
此时不管代码执行left=middle+1还是right=middle-1,返回left或者right+1均可。 - 目标值在数组所有元素之后的情况 [left, right]
left的值不断增大到right,最后left-right=1,返回left或者right+1均可。
JS 代码
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let middle = left + parseInt((right - left) / 2);
if (target > nums[middle]) {
left = middle + 1;
}else if (target < nums[middle]){
right = middle - 1;
}else{
return middle;
}
}
return right + 1;
//OR return left
};
2.2 左闭右开写法
基于上个题目的理论,分别处理如下四种情况:
- 目标值在数组所有元素之前 nums = [2,3,4], target = 1;
left的值为0不会改变,right的值最后参与循环会到0,返回left或者right均可。 - 目标值等于数组中某一个元素 return middle;
这种情况符合第一个题目。 - 目标值插入数组中的位置 [left, right]之间
当最后一次满足nums[left] < target < nums[right], 且right - left = 1;target的下标应该是right才对哦。
此时不管代码执行left = middle+1还是right = middle,返回left或者right均可。 - 目标值在数组所有元素之后的情况 [left, right]
left的值不断增大到right,最后left==right,返回left或者right均可。
GO 代码
func searchInsert(nums []int, target int) int {
left, right := 0, len(nums)
for left < right {
middle := left + (right-left)>>1
if target > nums[middle] {
left = middle + 1
} else if target < nums[middle] {
right = middle
} else {
return middle
}
}
return right //OR left
}