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

相关推荐
旖-旎1 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
To_OC1 小时前
LC 42 接雨水:暴力超时卡半天?前后缀数组一用就通了
javascript·算法·leetcode
delishcomcn2 小时前
AI视觉识别+分切算法:电化铝缺陷检测与裁切一体化解锁
人工智能·算法
触底反弹3 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
雪碧聊技术3 小时前
力扣 LCR 091. 粉刷房子 —— 动态规划入门详解
算法·动态规划
延凡科技5 小时前
多场景落地复盘:端边云架构无人机智能巡检系统设计与实践
大数据·数据结构·人工智能·科技·架构·无人机·能源
CV-Climber6 小时前
检索技术的实际应用
人工智能·算法
hhzz6 小时前
Tiger AI Platform平台中增加人脸识别功能
图像处理·人工智能·算法·计算机视觉·大模型
从零开始的代码生活_7 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法