【力扣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

相关推荐
BothSavage4 小时前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法
小林ixn4 小时前
从暴力到KMP:一道题彻底搞懂字符串匹配的前世今生
算法
烬羽5 小时前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试
先吃饱再说21 小时前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰1 天前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术1 天前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize1 天前
初识DFS 与 BFS:递归、队列与图遍历
算法