【力扣100题】76.搜索插入位置

题目描述

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

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

解题思路总览

方法 核心思想 时间复杂度 空间复杂度 特点
二分查找(标准版) 每次排除一半,逐步逼近目标 O(log n) O(1) 最常用,需掌握
二分查找(左边界版) 找第一个大于等于 target 的位置 O(log n) O(1) 本质与标准版相同,写法不同
lower_bound 模拟 模拟 C++ STL lower_bound O(log n) O(1) 标准库思想
直接遍历(不推荐) 线性扫描数组 O(n) O(1) 不满足题目要求,仅作对比

方法一:标准二分查找

代码实现

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return l;
    }
};

算法流程图

复制代码
目标:在有序数组 [1,3,5,6] 中查找 target = 2

初始状态:l = 0, r = 3

第1轮比较:
  l=0, r=3, mid=1
  nums[1]=3 > 2,右半部分排除
  r = mid - 1 = 0

第2轮比较:
  l=0, r=0, mid=0
  nums[0]=1 < 2,左半部分排除
  l = mid + 1 = 1

第3轮比较:
  l=1, r=0
  l > r,循环结束
  return l = 1

结果:插入位置为 1

逐行解析

cpp 复制代码
int n = nums.size();

获取数组长度 n,用于初始化右边界。


cpp 复制代码
int l = 0, r = n - 1;

初始化左右边界指针:

  • l 指向数组起始位置(候选插入位置的最左端)
  • r 指向数组末尾位置(候选插入位置的最右端)

cpp 复制代码
while (l <= r) {

循环条件是 l <= r,当 l > r 时说明搜索空间已经缩小到空集,循环终止。


cpp 复制代码
int mid = l + (r - l) / 2;

计算中间位置。使用 l + (r - l) / 2 而非 (l + r) / 2,是为了防止大数相加时的整数溢出。


cpp 复制代码
if (nums[mid] == target) {
    return mid;
}

找到目标值,直接返回索引。


cpp 复制代码
} else if (nums[mid] < target) {
    l = mid + 1;
}

中间值小于目标值,说明目标值若存在,必在 mid+1, r 区间,因此将左边界右移。


cpp 复制代码
} else {
    r = mid - 1;
}

中间值大于目标值,说明目标值若存在,必在 l, mid-1 区间,因此将右边界左移。


cpp 复制代码
return l;

循环终止时,l 的位置即为插入位置。此时 l > r,l 是第一个大于 target 的位置。

复杂度分析

复杂度 分析
时间 每次循环将区间缩小一半,最多循环 log2(n) 次
空间 仅使用常数个变量(n, l, r, mid),空间复杂度 O(1)

方法二:左边界二分查找

代码实现

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int l = 0, r = n;  // 注意:右边界初始化为 n,而非 n-1
        while (l < r) {    // 注意:循环条件是 l < r
            int mid = l + (r - l) / 2;
            if (nums[mid] < target) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        return l;
    }
};

与方法一的对比

对比项 方法一(标准版) 方法二(左边界版)
右边界初始化 r = n - 1 r = n
循环条件 l <= r l < r
numsmid < target 时 l = mid + 1 l = mid + 1
numsmid >= target 时 r = mid - 1 r = mid
返回值 l(需理解推导) l(直观理解:第一个 >= target 的位置)

核心区别解释

方法二的循环条件是 l < r,不会出现 l > r 的情况。当循环结束时,l == r,此时 l 指向第一个大于等于 target 的位置,也就是插入位置。

这种写法更符合"找左边界"的思想,语义更清晰。

算法流程图

复制代码
目标:在有序数组 [1,3,5,6] 中查找 target = 2

初始状态:l = 0, r = 4(n=4)

第1轮比较:
  l=0, r=4, mid=2
  nums[2]=5 >= 2,r = mid = 2

第2轮比较:
  l=0, r=2, mid=1
  nums[1]=3 >= 2,r = mid = 1

第3轮比较:
  l=0, r=1, mid=0
  nums[0]=1 < 2,l = mid + 1 = 1

第4轮比较:
  l=1, r=1
  l == r,循环结束
  return l = 1

结果:插入位置为 1

逐行解析

cpp 复制代码
int l = 0, r = n;

右边界初始化为 n(数组长度),表示搜索区间为 [0, n),即整个数组范围加上"插入到末尾"的可能性。


cpp 复制代码
while (l < r) {

循环条件是 l < r,当 l == r 时循环终止,此时 l 指向目标位置。


cpp 复制代码
int mid = l + (r - l) / 2;

计算中间位置。注意:mid 始终小于 r,因此不会发生死循环。


cpp 复制代码
if (nums[mid] < target) {
    l = mid + 1;
} else {
    r = mid;
}

关键逻辑:

  • 如果 numsmid 小于 target,说明插入位置在 mid 右侧,更新 l = mid + 1
  • 如果 numsmid 大于等于 target,说明插入位置在 mid 或其左侧,更新 r = mid

cpp 复制代码
return l;

循环结束时 l == r,l 指向第一个大于等于 target 的位置,即为插入位置。

复杂度分析

复杂度 分析
时间 每次循环将区间缩小一半,最多循环 log2(n) 次
空间 仅使用常数个变量,空间复杂度 O(1)

方法三:模拟 lower_bound

代码实现

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        return lower_bound(nums.begin(), nums.end(), target) - nums.begin();
    }
    
private:
    int lower_bound(vector<int>::iterator begin, 
                   vector<int>::iterator end, 
                   int target) {
        while (begin < end) {
            auto mid = begin + (end - begin) / 2;
            if (*mid < target) {
                begin = mid + 1;
            } else {
                end = mid;
            }
        }
        return begin - nums.begin();
    }
};

核心思想

lower_bound 是 C++ STL 中的标准库函数,返回第一个大于等于给定值的迭代器。本方法模拟了这一实现。

复杂度分析

复杂度 分析
时间 O(log n)
空间 O(1)(不含输入输出空间)

方法四:直接遍历(不推荐)

代码实现

cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] >= target) {
                return i;
            }
        }
        return nums.size();
    }
};

算法流程图

复制代码
目标:在有序数组 [1,3,5,6] 中查找 target = 2

遍历数组:
  i=0: nums[0]=1 < 2,继续
  i=1: nums[1]=3 >= 2,找到插入位置
  return 1

结果:插入位置为 1

复杂度分析

复杂度 分析
时间 最坏情况遍历整个数组 O(n)
空间 O(1)

不推荐原因

题目要求时间复杂度为 O(log n),直接遍历不满足要求。但该方法思路简单,在面试中可以作为"最直观解法"先写出,再优化为二分查找。


边界情况分析

情况1:数组为空

复制代码
输入: nums = [], target = 5
分析: n = 0,l = 0,r = -1
     循环不执行,直接 return l = 0
结果:正确,插入位置为 0

情况2:目标值小于所有元素

复制代码
输入: nums = [1,3,5,6], target = 0
分析: 
  第1轮:mid=1, nums[1]=3 > 0,r=0
  第2轮:mid=0, nums[0]=1 > 0,r=-1
  循环结束,return l = 0
结果:正确,插入位置为 0

情况3:目标值大于所有元素

复制代码
输入: nums = [1,3,5,6], target = 7
分析:
  第1轮:mid=1, nums[1]=3 < 7,l=2
  第2轮:mid=2, nums[2]=5 < 7,l=3
  第3轮:mid=3, nums[3]=6 < 7,l=4
  循环结束,l = 4
结果:正确,插入位置为 4(数组末尾)

情况4:目标值等于首元素

复制代码
输入: nums = [1,3,5,6], target = 1
分析: mid=1, nums[1]=3 > 1,r=0
     mid=0, nums[0]=1 == 1
结果:直接返回 0

情况5:目标值等于末元素

复制代码
输入: nums = [1,3,5,6], target = 6
分析: 逐步二分,最终 nums[3] == 6
结果:直接返回 3

面试追问 FAQ

问题 回答
为什么返回 l 而不是 r? 循环结束时 l > r(标准版)或 l == r(左边界版)。l 指向第一个大于目标值的位置,也就是目标值应该插入的位置
如何处理大整数溢出? 使用 mid = l + (r - l) / 2 代替 mid = (l + r) / 2,避免相加溢出
如果数组为空会怎样? n = 0,标准版 r = -1,循环不执行直接返回 l = 0;左边界版 r = 0,循环不执行直接返回 l = 0
如何处理重复元素? 标准版返回任意一个等于 target 的索引;左边界版返回第一个等于 target 的位置(因为找的是 >= target 的左边界)
二分查找的变形有哪些? 查找左边界、查找右边界、搜索旋转排序数组、寻找峰值等
循环条件 l <= rl < r 有什么区别? l <= r 最后会得到 l > r;l < r 最后会得到 l == r。两者都能完成二分,区别在于初始边界和返回值含义的理解
为什么左边界版本把 r 初始化为 n 而不是 n-1? 因为搜索区间是 [0, n),即包含"插入到末尾"的情况。如果初始化为 n-1,当 target 大于所有元素时无法正确返回 n

相关题目

题目 难度 核心区别
34. 在排序数组中查找元素的第一个和最后一个位置 困难 需要同时找左边界和右边界
74. 搜索二维矩阵 中等 二维矩阵的二分查找
240. 搜索二维矩阵 II 中等 每行每列均递增
278. 第一个错误的版本 简单 二分查找的简单应用
704. 二分查找 简单 标准二分查找
875. 爱吃香蕉的可莉娜 中等 二分查找与单调性

总结

要点 说明
核心思想 利用有序数组的单调性,每次排除一半元素,将搜索范围缩小到 O(log n)
关键变量 l(左边界)、r(右边界)、mid(中间位置)
终止条件 标准版 l > r;左边界版 l == r
返回值 l 即为插入位置(第一个大于等于 target 的位置)
时间复杂度 O(log n)
空间复杂度 O(1)
防溢出技巧 使用 mid = l + (r - l) / 2 代替 mid = (l + r) / 2

相关推荐
Techblog of HaoWANG1 小时前
智巡守卫:多模态巡检智能体算法服务端设计与实现——基于Ollama+Qwen3.5的自动化巡检报告生成系统
运维·人工智能·算法·目标检测·自动化·边缘计算
小蒋学算法1 小时前
算法-灌溉花园的最少龙头数目-贪心
算法
满怀冰雪1 小时前
第07篇-差分算法-高效处理区间修改问题
数据结构·算法
KaMeidebaby1 小时前
卡梅德生物技术快报|重组蛋白的表达和纯化:工艺调试全记录:大肠杆菌体系重组蛋白的表达和纯化参数标定(肠激酶轻链案例)
前端·人工智能·算法·数据挖掘·数据分析
ZPC82102 小时前
如何将机械臂末端定位精度提升至微米如何进行标定
人工智能·算法·机器人
wabs6662 小时前
关于动态规划【力扣343.整数拆分的递推公式怎么理解?】
算法·leetcode·动态规划
测试狗科研平台2 小时前
第一性原理CO2还原反应计算流程和软件推荐
科技·算法·云计算
SEO_juper2 小时前
2026 谷歌 SEO&GEO 常见问题合集:收录、排名、内容、技术全解析
算法·谷歌·常见问题·seo·跨境电商·外贸·geo
叫我:松哥2 小时前
基于卷积神经网络的静态手势语识别算法,在测试集上的识别准确率达到97.5%
人工智能·python·深度学习·神经网络·算法·cnn