35. 搜索插入位置 (Easy)
专题归类: 09-二分查找 LeetCode 链接: https://leetcode.cn/problems/search-insert-position/
在线做题: 写 Python 代码并运行 · Java 跳转 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
示例 4:
输入:nums = [1,3,5,6], target = 0
输出:0
提示:
1 <= nums.length <= 10^4-10^4 <= nums[i] <= 10^4nums为无重复元素的升序排列数组-10^4 <= target <= 10^4
题目详细分析
数据范围含义:
nums.length <= 10^4:O(log n) 二分最多 14 次比较,非常快。- 无重复元素:简化了查找——每个值最多出现一次,不需要处理重复。
- 升序排列:二分查找的前提条件。
问题本质:
- 这是一个”查找左边界”问题——找第一个 >= target 的元素位置。
- 如果 target 存在,返回其索引(第一个等于 target 的位置)。
- 如果 target 不存在,返回它应该插入的位置(第一个大于 target 的位置)。
- 插入位置的范围是 [0, len(nums)],包括在开头(所有元素都大)和在末尾(所有元素都小)。
边界条件:
- target 小于所有元素 → 返回 0。
- target 大于所有元素 → 返回 len(nums)(插入到末尾)。
- target 等于某个元素 → 返回该元素索引。
小白版直白理解
就像翻字典查单词:
- 字典是排好序的(a → z)。
- 如果要查的单词存在,就告诉你它在哪一页。
- 如果不存在,就告诉你它应该插在哪一页之间。
比如在 [1,3,5,6] 中找 2:
- 先看中间 5,2<5,所以在前半段 [1,3] 中找。
- 再看中间 1,2>1,所以在后半段 [3] 中找。
- 再看 3,2<3,所以 2 应该插在 1 和 3 之间,位置是 1。
解题思路
思路一:左闭右闭区间二分(推荐)
核心思想: 维护 [left, right] 闭区间,每次排除一半不可能的区域。循环结束时,left 就是第一个 >= target 的位置。
关键洞察: 当 nums[mid] < target 时,mid 及左侧都 < target,一定能被排除,所以 left = mid + 1;否则(nums[mid] >= target),mid 可能是答案,所以 right = mid - 1,但 left 不会越过这个位置。
搜索过程可视化(以 nums=[1,3,5,6], target=2 为例):
初始: left=0, right=3
第1步: mid=1, nums[1]=3 >= 2, right=0
第2步: mid=0, nums[0]=1 < 2, left=1
第3步: left=1 > right=0, 退出
返回 left=1
def searchInsert(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid # 找到目标,直接返回
elif nums[mid] < target:
left = mid + 1 # 目标在右半
else:
right = mid - 1 # 目标在左半
# 循环结束时 left > right,left 即插入位置
return left思路二:左边界二分模板
核心思想: 统一使用”找第一个 >= target 的位置”的二分模板,不论 target 是否存在,结果都是正确的。
def searchInsert(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1 # mid 太小,收缩左边界
else:
right = mid - 1 # nums[mid] >= target,收缩右边界
return left # left 就是第一个 >= target 的位置时间复杂度: O(log n) 空间复杂度: O(1)
易错点
- 区间不变量搞混:使用
left <= right(闭区间)时,更新为mid + 1/mid - 1;使用left < right(开区间)时,更新方式不同。统一使用一种风格避免混淆。 - 返回
left还是right:循环结束时,left是第一个 >= target 的位置;right是left - 1。要注意返回的是left。 - 越界风险:
mid = (left + right) // 2在 left+right 很大时可能溢出,用left + (right - left) // 2更安全。 - 死循环:当
left == right时,如果更新逻辑不恰当(例如left = mid而不是mid + 1),可能出现死循环。使用left <= right闭区间的风格,配合mid +/- 1的更新,不会死循环。 - 插入末尾:如果 target 大于所有元素,最终
left = len(nums),这就是插入到末尾的位置,不能越界访问nums[left]。
框架提炼
二分查找左闭右闭模板:
def binary_search(nums, target):
left, right = 0, len(nums) - 1
while left <= right: # 闭区间 [left, right]
mid = left + (right - left) // 2
if nums[mid] == target:
return mid # 查找精确值
elif nums[mid] < target:
left = mid + 1 # 排除左半
else:
right = mid - 1 # 排除右半
# left 是第一个 > target 的位置(即插入位置)
return left三种典型二分场景:
| 场景 | 返回条件 | 典型题目 |
|---|---|---|
| 精确查找 | nums[mid] == target 时返回 mid | 704-二分查找 |
| 找左边界 | 找第一个 >= target,返回 left | 35-搜索插入位置 |
| 找右边界 | 找最后一个 <= target,返回 right | 34-查找元素首末位置 |
关键记忆: 二分查找的本质是不断缩小”目标可能存在”的区间,关键在于每次排除一半的确定无效区域。
关联题目
- 34-在排序数组中查找元素首末位置 — 二分边界问题的进阶版,需要同时查找左边界和右边界。
- 704-二分查找 — 最基础的标准二分查找,本题是左边界变体。
- 69-x的平方根 — 同样使用二分查找左边界思想,在 [0, x] 范围内找平方根。
- 278-第一个错误的版本 — 左边界二分的经典应用,找到第一个返回 true 的版本。