启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
该题是数组二分查找题型第二题。
题目来源
题目描述(简单)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 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
提示
nums为 无重复元素 的 升序 排列数组
题目解析
该题主要分两种情况:
- 目标值等于数组中某一个元素
- 目标值不等于数组中任一元素
- 目标值小于数组中所有元素
- 目标值在数组中的两个元素之间
- 目标值大于数组中的所有元素
暴力解法
可以将该题分为 目标值小于等于数组中的某一个元素 和 目标值大于数组中所有的元素 两种情况。在遍历数组过程中,由于数组是升序排列数组,当出现 目标值小于数组中所有元素 时,此时循环为数组第一项,目标值应该插入数组第一项,即下标 0 ;当出现 目标值等于数组元素 时,此时数组包含目标值,应该返回当前下标值,即 i ;当出现 目标值大于数组元素 时,此时目标值小于当前项并且大于前一项,应该返回当前下标值,即 i 。总而言之,当出现 目标值小于等于数组中的某一个元素 时,返回此时下标值即可,最后循环结束,便是 目标值大于数组中所有的元素 的情况,返回数组长度即可。
代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
let length = nums.length
for (let i = 0;i < length;i++){
//包括了三种情况
//目标值等于数组中某一个元素
//目标值小于数组中所有元素
//目标值在数组中的两个元素之间
if(nums[i] >= target){
return i
}
}
//目标值大于数组中的所有元素
return length
};
- 时间复杂度:O(n)
- 空间复杂度:O(1)
如图:
二分法(左闭右闭)
仔细观察题目条件,会发现题目一直强调数组为排序数组且无重复元素。满足二分查找的前提条件。
关于二分查找的边界处理可以参考我上一篇文章。 (LeetCode —— 704. 二分查找 - 掘金 (juejin.cn))
代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
// 首先定义二分区间为左闭右闭区间[left,right]
let left = 0, right = nums.length -1
// 当left === right时,在[left,right]是有意义的
while(right >= left){
let mid = Math.floor((left + right) / 2)
if(nums[mid] > target){
// target在左区间,所以区间为[left,mid - 1]
right = mid - 1
}else if (nums[mid] < target){
// target在右区间,所以区间为[mid + 1,right]
left = mid + 1
}else{
// target === nums[mid]
return mid
}
}
// 当目标值小于数组所有元素时,区间为[0,-1]
/* 当目标值在数组的两个元素之间
最后第二次循环中,left === right - 1
由于 mid = Math.floor((left + right) / 2) = Math.floor(left + 1/2 ) = left
此时 nums[mid] = nums[left] < target
所以 left = mid + 1 = right
最后一次循环中,left === right , mid = left = right , nums[mid] = nums[right] > target
所以 right = mid - 1 = left - 1
left > right
*/
// 当目标值大于数组中所有元素时,right = nums.length - 1 ,因为是右闭区间,所以 return right + 1
return right + 1
};
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
如图:
二分法(左闭右开)
仔细观察题目条件,会发现题目一直强调数组为排序数组且无重复元素。满足二分查找的前提条件。
关于二分查找的边界处理可以参考我上一篇文章。 (LeetCode —— 704. 二分查找 - 掘金 (juejin.cn))
代码
当区间为左闭右开时,此时二分法的边界处理便截然不同了。可以与上面代码比较并且思考为什么 while (right > left) 和 right = mid 此时循环条件为什么不加等号,右区间的赋值为什么不用 -1 。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
// 首先定义二分区间为左闭右闭区间[left,right)
let left = 0, right = nums.length
// 当left === right时,在[left,right)是没有意义的
while(right > left){
let mid = Math.floor((left + right) / 2)
if(nums[mid] > target){
// target在左区间,所以区间为[left,mid - 1)
right = mid
}else if (nums[mid] < target){
// target在右区间,所以区间为[mid + 1,right)
left = mid + 1
}else{
// target === nums[mid]
return mid
}
}
// 当目标值小于数组所有元素时,区间为[0,0]
/* 当目标值在数组的两个元素之间
最后第二次循环中,left === right - 1
由于 mid = Math.floor((left + right) / 2) = Math.floor(left + 1/2 ) = left
此时 nums[mid] = nums[left] < target
所以 left = mid + 1 = right
最后一次循环中,left === right,循环退出
所以 return right
*/
// 当目标值大于数组中所有元素时,right = nums.length ,因为是右开区间,所以 return right
return right
};
不难发现此时 right 要么不存在,要么是已经和目标值进行过比较。
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
如图:
执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。