简单
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 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
提示:
1 <= nums.length <= 104-104 <= nums[i] <= 104nums为 无重复元素 的 升序排列数组-104 <= target <= 104
📝 核心笔记:搜索插入位置 (Search Insert Position)
1. 核心思想 (一句话总结)
"寻找第一个大于等于 Target 的位置。"
lowerBound 的定义是:在一个有序数组中,找到第一个满足 nums[i] >= target 的下标 i。
- 如果
target存在,它返回第一个target的下标。 - 如果
target不存在,它返回target应当插入的位置(保持数组有序)。
2. 算法流程 (左闭右开版)
- 区间定义 :
[left, right)。
-
left = 0。right = nums.length(注意不是length-1,因为区间右边是开的,取不到length)。
- 循环条件 :
left < right。
-
- 因为是左闭右开,当
left == right时,区间变成[k, k),里面没元素了,循环自然结束。
- 因为是左闭右开,当
- 区间收缩 (关键):
-
- 太小了 ( nums[mid] < target**)** :
target肯定在右边,且mid肯定不是答案。
- 太小了 ( nums[mid] < target**)** :
-
-
left = mid + 1(左闭,跳过mid)。
-
-
- 够大了 ( nums[mid] >= target**)** :
mid可能是答案,也可能答案在更左边。
- 够大了 ( nums[mid] >= target**)** :
-
-
right = mid(右开,把mid设为右边界,下一次搜索范围... ~ mid-1)。
-
🔍 代码回忆清单 (带注释版)
// 题目:LC 35. Search Insert Position
class Solution {
public int searchInsert(int[] nums, int target) {
return lowerBound2(nums, target);
}
// 左闭右开区间写法 [left, right)
private int lowerBound2(int[] nums, int target) {
int left = 0;
int right = nums.length; // ⚠️ 注意 1: 右边界是 length (取不到)
// ⚠️ 注意 2: 条件是 < 不是 <=
// 当 left == right 时,区间 [left, right) 为空,退出循环
while (left < right) {
// 防止溢出的中间位置计算
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 目标在右边,mid 肯定不是,+1 跳过
left = mid + 1; // 范围缩小到 [mid+1, right)
} else {
// nums[mid] >= target
// 目标在左边,或者就是 mid
// 因为是右开区间,right = mid 代表下一次搜索不包含 mid
right = mid; // 范围缩小到 [left, mid)
}
}
// 结束时 left == right,返回谁都行
return left;
}
}
⚡ 快速复习 CheckList (易错点)
|---------------|-----------------------|------------------------|
| 维度 | 左闭右开 [L, R) (推荐) | 左闭右闭 [L, R] (常见) |
| Right 初始化 | nums.length | nums.length - 1 |
| While 条件 | left < right | left <= right |
| 收缩 Right | right = mid | right = mid - 1 |
| 终止状态 | left == right | left == right + 1 |
| 思维负担 | 低 (无需考虑 mid-1 ) | 中 (需考虑越界和漏查) |
-
\] **为什么是** **right = mid****?**
-
- 如果
nums[mid] >= target,说明mid可能是目标位置。 - 因为区间是 右开 的,我们把
right设为mid,意味着下一轮搜索区间是[left, mid)。 - 这样下一轮确实排除了 mid (因为它取不到),但逻辑上
mid并没有被丢弃,因为如果最后left追上来撞到right(也就是撞到mid),循环结束,mid就成了答案。
- 如果
-
\] **最大值越界?**
-
- 如果数组所有数都
< target,left会一直+1,最后变成nums.length。 - 这也是符合预期的(插在数组末尾),不会产生
ArrayIndexOutOfBounds,因为我们只返回下标,不访问nums[left]。
- 如果数组所有数都
🖼️ 数字演练
数组 nums = [1, 3, 5, 6], target = 2
- Init :
L=0, R=4. 区间[0, 4).
-
mid = 2(val=5).5 >= 2(大了)。- Right = 2 。区间变
[0, 2).
- Loop 2 :
L=0, R=2.
-
mid = 1(val=3).3 >= 2(大了)。- Right = 1 。区间变
[0, 1).
- Loop 3 :
L=0, R=1.
-
mid = 0(val=1).1 < 2(小了)。- Left = 1 。区间变
[1, 1).
- End :
L == R(均为 1),退出。
-
- 返回
1。 - 验证:
2确实应该插在1和3之间,下标是 1。
- 返回
(完美符合逻辑)