力扣解题-35. 搜索插入位置

4 阅读6分钟

力扣解题-35. 搜索插入位置

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

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

-10⁴ <= nums[i] <= 10⁴

nums 为 无重复元素 的 升序 排列数组

-10⁴ <= target <= 10⁴

Related Topics

数组、二分查找


第一次解答

解题思路

核心方法:二分查找边界定位法,利用升序数组的特性,通过二分查找缩小搜索范围,最终left指针的位置即为目标值的索引(存在时)或插入位置(不存在时),时间复杂度严格为O(logn)、空间复杂度O(1),完全满足题目要求且是最优解法。

核心逻辑拆解

二分查找找插入位置的核心规律:

  1. 二分查找的核心目标:在升序数组中,找到第一个大于等于目标值的元素位置(该位置就是插入位置);
  2. 指针移动规则
    • 若中间值<目标值:说明目标值在右半区间,将左指针移到mid+1
    • 若中间值目标值:说明目标值在左半区间(或就是当前值),将右指针移到mid-1
  3. 循环终止条件left > right,此时left恰好指向“第一个大于等于目标值的位置”,也就是插入位置。
具体执行逻辑
  1. 初始化指针
    • left:左边界,初始为0;
    • right:右边界,初始为nums.length-1(数组最后一个元素的索引);
  2. 二分循环:当left ≤ right时持续循环:
    • 计算中间索引mid = left + (right-left)/2(避免(left+right)/2的整数溢出);
    • 获取中间值midVal = nums[mid]
    • 比较midValtarget
      • midVal < target:目标值在右半区,更新left = mid + 1
      • 否则:目标值在左半区(或就是当前值),更新right = mid - 1
  3. 返回结果:循环结束后,left即为目标值的索引(存在)或插入位置(不存在)。
执行流程可视化(以示例2 nums=[1,3,5,6]、target=2为例)
循环次数leftrightmidmidVal比较结果指针更新说明
103133≥2right=0目标值在左半区
200011<2left=1目标值在右半区
循环结束10----left>right,返回1
关键细节说明
  • mid计算优化:使用left + (right-left)/2而非(left+right)/2,避免left+right超出int范围(如nums长度为1e4时无问题,但大数场景更安全);
  • 循环条件:必须是left <= right,而非left < right,确保能检查到最后一个元素;
  • 结果正确性
    • 目标值存在:循环中会定位到该值,最终left等于其索引;
    • 目标值不存在:left指向第一个大于目标值的位置(示例3中target=7,最终left=4,即数组末尾的插入位置);
  • 无重复元素:题目明确nums无重复,无需处理重复值的边界情况。
性能说明
  • 时间复杂度:O(logn)(每次循环将搜索范围缩小一半,最多循环log₂n次);
  • 空间复杂度:O(1)(仅使用常数级别的额外变量);
  • 优势:
    1. 严格满足题目O(logn)时间复杂度要求;
    2. 代码极简,仅需基础二分框架即可实现;
    3. 无需额外判断,循环结束直接返回left,逻辑闭环。
public int searchInsert(int[] nums, int target) {
    int left=0;
    int right=nums.length-1;
    while(left<=right){
        int mid=left+(right-left)/2;
        int midVal=nums[mid];
        if(midVal<target){
            left=mid+1;
        }else {
            right=mid-1;
        }
    }
    return left;
}

示例解答

解题思路

解法1:显式查找目标值的二分法(更易理解)

核心方法:在二分循环中显式判断是否找到目标值,找到则立即返回索引,未找到则最后返回left,逻辑与原解法一致,但更贴合“先查找、再插入”的直观思路。

代码实现
public int searchInsert(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            // 找到目标值,直接返回索引
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    // 未找到,返回插入位置
    return left;
}
核心逻辑说明
  1. 显式匹配:增加nums[mid] == target的判断,找到目标值时立即返回,减少不必要的循环;
  2. 逻辑等价性:与原解法逻辑完全等价,仅拆分了判断条件,最终返回的left值一致;
  3. 可读性提升:新手更容易理解“先找目标值,找不到再返回插入位置”的流程。
性能说明
  • 时间复杂度:O(logn)(最优情况找到目标值时提前返回,效率更高);
  • 空间复杂度:O(1)(与原解法一致);
  • 优势:
    1. 可读性更强,符合“查找→插入”的直观思维;
    2. 找到目标值时提前终止循环,减少迭代次数;
  • 劣势:代码量略多一行判断,无本质性能差异。
解法2:左闭右开区间的二分法(进阶写法)

核心方法:将搜索区间定义为左闭右开[left, right),右边界初始为nums.length,通过调整指针移动规则实现二分查找,是二分查找的另一种经典范式。

代码实现
public int searchInsert(int[] nums, int target) {
    int left = 0;
    int right = nums.length; // 右边界为数组长度(左闭右开)
    
    while (left < right) { // 循环条件为left < right
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1; // 左区间仍为闭区间
        } else {
            right = mid; // 右区间为开区间,不包含mid
        }
    }
    // left == right,即为插入位置
    return left;
}
核心逻辑说明
  1. 区间定义[left, right)表示当前搜索区间,初始为[0, nums.length),覆盖所有可能的插入位置(0到nums.length);
  2. 循环条件left < right,因为右开区间无需等于;
  3. 指针移动
    • nums[mid] < target:目标值在右半区,left = mid + 1(左闭);
    • nums[mid] ≥ target:目标值在左半区,right = mid(右开);
  4. 结果一致性:最终left == right,值与原解法的left完全一致。
性能说明
  • 时间复杂度:O(logn)(与原解法一致);
  • 空间复杂度:O(1)(与原解法一致);
  • 优势:
    1. 无需处理right = mid - 1的减1操作,减少出错概率;
    2. 区间定义更符合编程中“左闭右开”的常用习惯(如数组切片);
  • 劣势:需理解左闭右开的区间逻辑,新手入门门槛略高。

总结

  1. 二分查找边界定位法(第一次解答):O(logn)时间+O(1)空间,左闭右闭区间范式,代码极简,是本题的核心最优解;
  2. 显式查找目标值的二分法:O(logn)时间+O(1)空间,可读性更强,找到目标值时提前返回;
  3. 左闭右开区间的二分法:O(logn)时间+O(1)空间,进阶范式,减少指针减1操作;
  4. 关键技巧
    • 核心思想:插入位置本质是“第一个大于等于目标值的元素索引”,二分查找的核心是缩小该位置的范围;
    • 区间选择:左闭右闭/左闭右开均可,关键是保持区间定义的一致性;
    • 结果保证:无论哪种范式,最终返回的left都是正确的插入位置/目标值索引。