LeetCode —— 35. 搜索插入位置

119 阅读4分钟

启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

该题是数组二分查找题型第二题。

数组专栏 - 掘金

题目来源

35. 搜索插入位置 (LeetCode)

题目描述(简单

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 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

提示

  • 1<=nums.length<=1041 <= nums.length <= 10^4
  • 104 <=nums[i]<=104-10^4 <= nums[i] <= 10^4
  • nums无重复元素升序 排列数组
  • 104<=target<=104-10^4 <= target <= 10^4

题目解析

该题主要分两种情况:

  • 目标值等于数组中某一个元素
  • 目标值不等于数组中任一元素
    • 目标值小于数组中所有元素
    • 目标值在数组中的两个元素之间
    • 目标值大于数组中的所有元素

image.png

暴力解法

可以将该题分为 目标值小于等于数组中的某一个元素目标值大于数组中所有的元素 两种情况。在遍历数组过程中,由于数组是升序排列数组,当出现 目标值小于数组中所有元素 时,此时循环为数组第一项,目标值应该插入数组第一项,即下标 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)

如图:

image.png

二分法(左闭右闭)

仔细观察题目条件,会发现题目一直强调数组为排序数组且无重复元素。满足二分查找的前提条件。

关于二分查找的边界处理可以参考我上一篇文章。 (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)

如图:

image.png

二分法(左闭右开)

仔细观察题目条件,会发现题目一直强调数组为排序数组且无重复元素。满足二分查找的前提条件。

关于二分查找的边界处理可以参考我上一篇文章。 (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)

如图:

image.png

执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。