搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
典型二分法。二分查找思路很简单,排序后的数据,假设数组中间数据比目标大,那么结果一定在数组前半段。反之,如果中间数据比目标小,那么结果一定在后半段。同时还要兼顾,数组中全部数据都比目标大或小的情况。
典型且简单,很适合用来归纳思考方式。
function main(nums, target) {
// 左右指针
let left = 0,
right = nums.length - 1;
// 结果,假设全部数据都小于,需要插入尾部
let result = nums.length;
while (left <= right) {
// 二分,防止溢出
const half = left + Math.floor((right - left) / 2);
const halfVal = nums[half];
// 相等获取 index
if (halfVal === target) return half;
// 大于证明,需要向左找
// 假设全部元素都大于,最终遍历结束, left = right = 0
if (halfVal > target) {
right = half - 1;
// 假设找不到,需要保留最后一个大于 target 的位置
result = half;
} else {
// 小于证明,需要向右找
// 假设全部都小于, left = right = nums.length
// 一直小于没有关系,默认情况数组所有值都小于目标
left = half + 1;
}
}
return result;
}
查找中间点操作,也有快慢指针的方式查找。O(n) 操作,放在这里得不偿失。一开始想到的就是 (left + right) / 2 取整,这样计算有溢出风险。假设 right 值超出数字上限一半以上,二分收束到最右边会出现left + right > Number.MIN_VALUE。所以转换成,left + Math.floor((right - left) / 2),这样即使 right 已经到达 Number.MIN_VALUE,这里所有的操作也都低于最大值。
比较后,中间位置和目标值大小关系确定。下次比较的区间,无需包含中间位置,如 right = half - 1;。包含会有问题,假设 nums = [1, 2], target = 2,初始位置 left = 0, right = 1, half = 0,假设 left = half; 就会变成一个死循环。
另外一定记得考虑,全部大于目标或者小于的情况,代码中有注释。
总结,考虑二分,先考虑全部找不到的情况,包括查找到最右侧,和最左侧。然后开始思考,一两个元素的情况,看指针式怎么移动的。