LeetCode 35. 搜索插入位置
问题描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。
示例:
输入: nums = [1,3,5,6], target = 5
输出: 2
输入: nums = [1,3,5,6], target = 2
输出: 1
输入: nums = [1,3,5,6], target = 7
输出: 4
算法思路
二分查找法
:
- 初始化边界:
- 左指针
left = 0
- 右指针
right = nums.length - 1
- 左指针
- 二分查找:
- 当
left <= right
时循环:- 计算中点
mid = left + (right - left) / 2
(防溢出) - 若
nums[mid] == target
→ 直接返回mid
- 若
nums[mid] < target
→left = mid + 1
- 若
nums[mid] > target
→right = mid - 1
- 计算中点
- 当
- 返回插入位置:
- 循环结束后,
left
即为目标值应插入的位置
- 循环结束后,
关键
- 循环终止条件:
left <= right
确保所有元素都被检查 - 插入位置:
- 目标值存在时:直接返回索引
- 目标值不存在时:
- 目标值小于所有元素 →
left=0
- 目标值大于所有元素 →
left=nums.length
- 目标值在数组中间 →
left
指向第一个大于目标值的位置
- 目标值小于所有元素 →
代码实现
class Solution {
/**
* 在排序数组中查找目标值或插入位置
*
* @param nums 排序数组
* @param target 目标值
* @return 目标值索引或插入位置
*/
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
// 计算中间位置(防溢出写法) mid = left
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 找到目标值直接返回索引
return mid;
} else if (nums[mid] < target) {
// 目标值在右半区
left = mid + 1;
} else {
// 目标值在左半区
right = mid - 1;
}
}
// 循环结束时,left即为插入位置
return left;
}
}
算法分析
- 时间复杂度:O(log n)
- 每次循环将搜索范围减半
- 空间复杂度:O(1)
- 仅使用常数空间
算法过程
示例1:nums = [1,3,5,6], target = 2
- 初始:
left=0, right=3
mid=1
→nums[1]=3 > 2
→right=0
- 第二轮:
left=0, right=0
mid=0
→nums[0]=1 < 2
→left=1
- 循环结束:
left=1
示例2:nums = [1,3,5,6], target = 7
- 初始:
left=0, right=3
mid=1
→3<7
→left=2
- 第二轮:
left=2, right=3
mid=2
→5<7
→left=3
- 第三轮:
left=3, right=3
mid=3
→6<7
→left=4
- 返回
4
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:目标值存在数组中
int[] nums1 = {1,3,5,6};
System.out.println("Test 1: " + solution.searchInsert(nums1, 5)); // 2
// 测试用例2:目标值应插入中间
int[] nums2 = {1,3,5,6};
System.out.println("Test 2: " + solution.searchInsert(nums2, 2)); // 1
// 测试用例3:目标值大于所有元素
int[] nums3 = {1,3,5,6};
System.out.println("Test 3: " + solution.searchInsert(nums3, 7)); // 4
// 测试用例4:目标值小于所有元素
int[] nums4 = {1,3,5,6};
System.out.println("Test 4: " + solution.searchInsert(nums4, 0)); // 0
// 测试用例5:目标值应插入末尾前
int[] nums5 = {1,3,5,6};
System.out.println("Test 5: " + solution.searchInsert(nums5, 6)); // 3
// 测试用例6:空数组
int[] nums6 = {};
System.out.println("Test 6: " + solution.searchInsert(nums6, 5)); // 0
// 测试用例7:单元素数组(目标存在)
int[] nums7 = {5};
System.out.println("Test 7: " + solution.searchInsert(nums7, 5)); // 0
// 测试用例8:单元素数组(目标不存在)
int[] nums8 = {5};
System.out.println("Test 8: " + solution.searchInsert(nums8, 2)); // 0
}
关键点
-
二分查找核心:
- 通过比较中点值决定搜索方向
- 循环条件
left <= right
确保覆盖所有情况
-
插入位置确定:
- 循环结束时
left
总是指向第一个大于目标值的位置 - 若目标值大于所有元素,
left
会停在nums.length
- 循环结束时
-
边界处理:
- 空数组 → 返回0(插入位置)
- 单元素数组 → 根据比较结果返回0或1
常见问题
-
为什么循环条件是
left <= right
?
当left == right
时,区间仍有一个元素需要检查,若使用left < right
会漏查单个元素的情况。 -
为什么返回
left
而不是right
?- 当目标值小于中点时:
right = mid - 1
,而left
保持指向第一个大于目标值的位置 - 当目标值大于中点时:
left = mid + 1
,正好指向应插入位置 - 例如
target=2
在[1,3]
中,最终left=1, right=0
,插入位置为1
- 当目标值小于中点时:
-
如何处理重复元素?
当存在重复元素时,二分查找会返回任意一个匹配位置(题目未要求返回第一个或最后一个),但插入位置计算逻辑不变。 -
防溢出写法
mid = left + (right - left)/2
的意义?
避免(left + right)
可能超过整数最大值的情况,提高代码健壮性。 -
若数组中有多个相同目标值怎么办?
题目未明确要求,通常返回任意一个目标值的索引即可。例如[1,2,2,3]
中搜索2
,可能返回索引1或2。