[路飞]_前端算法第六弹-搜索插入位置

124 阅读2分钟

「这是我参与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-1left+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
};

只要确定是[]还是[),以后一直坚持这个闭合空间就好。