持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情
题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
输入: nums = [1,3,5,6], target = 5
输出: 2
输入: nums = [1,3,5,6], target = 2
输出: 1 提示:nums为 无重复元素 的 升序 排列数组
解题思路:
二分查找法
- 从数组中间的元素开始,如果中间的元素正好是目标值,搜索结束
- 如果目标值大于或小于中间的元素,则在大于或小于中间的元素的那一半继续搜索
注:数组为有序数组(升序),并且无重复元素。因为有重复元素,使用二分查找法返回的元素下标可能不是唯一的
特殊判断: 比较目标元素和数组中的最后一个元素,如果大于,返回数组中的最后一个元素的下标加1(目标元素插入的位置)。右侧下标right 设置成数组长度,一开始就不需要特殊判断了。
-
定义左侧下标left为0,右侧下标right为
nums.length, 在数组该区间里查找第一个大于等于target的元素下标 ; 超出区间,直接返回左侧下标left -
数组中间值 和 目标元素 进行大小判断,nums[mid] < target 则 left 右移(mid+1),nums[mid] >= target 则 right 移到中间位置(mid)
-
while (left < right) 表示当 left 与 right 重合的时候,搜索终止。退出循环的时候,必然满足
left == right,因此返回 left 或者 right 都可以。
var searchInsert = function(nums, target) {
const len = nums.length;
// 特殊判断 如果目标元素严格大于数组中的最后一个元素,将返回数据的最后一个元素的下标加一(也就是数组长度)
// if(target > nums[len - 1]){
// return len;
// }
// target <= nums[len - 1],插入位置在区间[0, len-1]
let left = 0;
let right = len;
// 在区间nums[left,right)里查找第一个大于等于target的元素下标
while(left < right){
//中间值
let mid = Math.floor((left + right) / 2);
// 情况1 如果中间位置的数值严格小于目标值(target),那么mid以及mid左边的就一定不是插入元素位置,下一轮搜索区间[mid+1,right] left = mid+1
if(nums[mid] < target){
left = mid+1;
} else{
// 情况2 如果mid看到的数值大于等于target 那么mid可能是插入元素的位置,mid的右边一定不存在插入元素的位置,下一轮搜索区间[left,mid] right = mid
right = mid;
}
}
return left
};
知识点:时间复杂度
一般用“大O表示法”来表示时间复杂度:T(n) = O(f(n))
T(n): 表示代码的执行时间,
f(n): 表示代码的执行总次数,
n: 表示数据规模的大小
总体理解为代码的执行时间和代码的执行次数成正比,代码的执行时间随数据规模增长的变化趋势。
常见的时间复杂度量级
常数阶O(1)
代码执行时消耗的时间不受某个变量 (n) 的增长而影响,这样的代码复杂度就为 O(1)。
const a = n;
const b = 2 * n;
return a + b;
注:一般情况下除了循环语句、递归语句,时间复杂度都为 O(1)
线性阶O(n)
代码执行时消耗的时间随着变量(n)的变化而变化,这类代码都可以用O(n)来表示它的时间复杂度。
const n = 100;
for (let i = 0; i < n; i++) {
let sum = 0;
sum += i;
return sum;
}
对数阶O(logn)
时间复杂度:0(logn),其中n为数组的长度。二分查找所需的时间复杂度为0(logn)
let i = 1;
const n = 8;
while (i < n) {
i = i * 2;
}
i的变化规律: 2^0, 2^1, 2^2, 2^3
当循环3次后退出,也就是说2^3 = n。那么 3 = log2^n,得出时间复杂度为 O(logn)。二分查找的时间复杂度就是 O(logn)。