本文概览:本文讲解搜索插入位置的核心思路:在有序数组中查找 target,找到返回索引,找不到返回应该插入的位置
一、题目

二、题目分析
这题的要求是:给定一个有序数组 和一个目标值 target,在数组中找到目标值并返回其索引。如果目标值不存在,则返回它应该被按顺序插入的位置。
比如数组 [1, 3, 5, 7]:
- 查找
target = 3,返回索引1 - 查找
target = 4,返回插入位置2(插入后数组为[1, 3, 4, 5, 7])
这题可以用二分查找实现 O(log n) 的时间复杂度,前提是数组一定有序。
二分查找的思路就是:每次查询都查询目前查找范围的中间点,判断中间点的值大于还是小于 target。
思路概览
Java 实现代码如下
java
public int searchInsert(int[] nums, int target) {
int left = 0, 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;
}
思路简要说明:
- 初始范围 :
left = 0,right = nums.length - 1 - 中间点计算 :
mid = left + (right - left) / 2,避免溢出 - 范围更新 :小于 target 时
left = mid + 1,大于时right = mid - 1 - 终止条件 :
left > right说明搜索完毕,返回left就是插入位置
三、思路详解
第一步:为什么需要有序数组
二分查找的核心是"每次排除一半的搜索范围"。如果数组无序,中间点的值无法告诉我们 target 在左边还是右边,也就无法排除一半。
比如升序数组 [1, 3, 5, 7, 9],查找 target = 7:
- 中间点是 5,5 < 7,说明 7 一定在右边(因为数组升序,左边的值都小于 5)
- 排除左半部分,搜索范围变为
[7, 9] - 中间点是 7,找到目标
第二步:中间点的计算
中间点的计算公式是:
java
int mid = left + (right - left) / 2;
为什么不直接写 (left + right) / 2?
因为如果 left 和 right 都很大,left + right 可能溢出 int 的取值范围 。比如 left = 2^30,right = 2^30,相加就超过了 Integer.MAX_VALUE。
而 left + (right - left) / 2 是先算距离再除以 2,不会溢出。
第三步:范围更新
假设数组升序,每次查询中间点后:
- numsmid == target:找到目标,直接返回
- numsmid < target :target 一定在右边,更新
left = mid + 1 - numsmid > target :target 一定在左边,更新
right = mid - 1
为什么是 mid + 1 和 mid - 1,不是 mid?
因为 mid 已经检查过了,不是目标值,所以下一次搜索范围不包含 mid。
第四步:终止条件和插入位置
循环条件是 left <= right,当 left > right 时退出循环。
这说明搜索范围为空,没有找到 target。
关键问题:为什么返回 left 就是插入位置?
当循环结束时,left = right + 1,此时有两种情况:
- 最后一步是
left = mid + 1:说明nums[mid] < target,mid左边的值都小于 target,所以 target 应该插入到mid + 1的位置,也就是left - 最后一步是
right = mid - 1:说明nums[mid] > target,mid右边的值都大于 target,所以 target 应该插入到mid的位置,而此时left = mid(因为right = mid - 1,循环结束意味着left = right + 1 = mid)
所以无论哪种情况,返回 left 就是 target 应该插入的位置。
第五步:完整执行过程
例子 1:找到 target
以 nums = [1, 3, 5, 7, 9],target = 7 为例:
初始:left=0, right=4
第 1 轮:
mid = 0 + (4-0)/2 = 2
nums[2] = 5 < 7
left = mid + 1 = 3
第 2 轮:
left=3, right=4
mid = 3 + (4-3)/2 = 3
nums[3] = 7 == 7
返回 3 ✓
例子 2:找不到 target,返回插入位置
以 nums = [1, 3, 5, 7, 9],target = 6 为例:
初始:left=0, right=4
第 1 轮:
mid = 2, nums[2] = 5 < 6
left = 3
第 2 轮:
left=3, right=4
mid = 3, nums[3] = 7 > 6
right = 2
第 3 轮:
left=3, right=2
left > right,退出循环
返回 left = 3
6 应该插入到索引 3 的位置,插入后数组为 [1, 3, 5, 6, 7, 9]。
例子 3:插入到末尾
以 nums = [1, 3, 5],target = 7 为例:
初始:left=0, right=2
第 1 轮:
mid = 1, nums[1] = 3 < 7
left = 2
第 2 轮:
left=2, right=2
mid = 2, nums[2] = 5 < 7
left = 3
第 3 轮:
left=3, right=2
left > right,退出循环
返回 left = 3(插入到末尾)
例子 4:插入到开头
以 nums = [3, 5, 7],target = 1 为例:
初始:left=0, right=2
第 1 轮:
mid = 1, nums[1] = 5 > 1
right = 0
第 2 轮:
left=0, right=0
mid = 0, nums[0] = 3 > 1
right = -1
第 3 轮:
left=0, right=-1
left > right,退出循环
返回 left = 0(插入到开头)
第六步:时间复杂度分析
每次搜索范围减半,所以时间复杂度是 O(log n)。
比如数组长度 1024,最多查 10 次(2^10 = 1024),而线性查找最多要查 1024 次。
四、总结
搜索插入位置的核心是三个要素:
- 有序数组:这是前提
- 中间点计算 :
left + (right - left) / 2避免溢出 - 范围更新:根据中间点和 target 的大小关系,排除一半的搜索范围
- 插入位置 :循环结束时返回
left,就是 target 应该插入的位置
这个思路可以推广到所有"有序数组查找和插入"类题目。