LeetCode 35. 搜索插入位置
题目链接
35. 搜索插入位置
题目描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。要求算法时间复杂度为 O(log n)
。
三种解法对比分析
三种解法均基于二分查找,核心目标是找到“第一个大于等于目标值的位置”(即目标值的插入位置)。差异体现在二分查找的循环条件和边界处理上。
解法一:循环条件 left <= right
(经典二分)
from typing import Listclass Solution: def searchInsert(self, nums: List[int], target: int) -> int: left = 0 right = len(nums) - 1 # 初始右边界为数组最后一个元素的索引 while left <= right: # 当left > right时退出循环 m = (left + right) // 2 # 中间位置 if nums[m] >= target: # 中间值 >= 目标值,收缩右边界(寻找更左的位置) right = m - 1 else: # 中间值 < 目标值,收缩左边界 left = m + 1 # 循环结束后,left即为第一个 >= target的位置(插入位置) return left
核心特点
- 循环条件:
left <= right
,这是最经典的二分查找循环条件,当left > right
时退出循环。 - 边界设置:
- 左边界
left = 0
(数组起始索引)。 - 右边界
right = len(nums) - 1
(数组最后一个元素的索引)。
- 左边界
- 收缩逻辑:
- 若
nums[m] >= target
:目标值可能在左侧,右边界收缩至m - 1
(跳过当前m
,后续通过left
还原)。 - 若
nums[m] < target
:目标值必在右侧,左边界收缩至m + 1
(跳过当前m
)。
- 若
- 返回值:
left
,此时left > right
,且left
是第一个满足nums[left] >= target
的位置(若目标值不存在,即为插入位置)。
解法二:循环条件 left < right
from typing import Listclass Solution: def searchInsert(self, nums: List[int], target: int) -> int: left = 0 right = len(nums) # 初始右边界为数组长度(而非最后一个索引) while left < right: # 当left == right时退出循环 m = (left + right) // 2 # 中间位置 if nums[m] >= target: # 中间值 >= 目标值,收缩右边界(保留m作为候选) right = m else: # 中间值 < 目标值,收缩左边界 left = m + 1 # 循环结束后,left == right,即为插入位置 return left
核心特点
- 循环条件:
left < right
,当left == right
时退出循环,避免死循环。 - 边界设置:
- 左边界
left = 0
。 - 右边界
right = len(nums)
(数组长度,覆盖“目标值大于所有元素”的情况)。
- 左边界
- 收缩逻辑:
- 若
nums[m] >= target
:右边界收缩至m
(保留m
作为候选,因为可能是第一个 >= 目标值的位置)。 - 若
nums[m] < target
:左边界收缩至m + 1
(m
不可能是目标位置,直接跳过)。
- 若
- 返回值:
left
(此时left == right
),直接对应目标值的插入位置。
解法三:循环条件 left + 1 < right
(开区间处理)
from typing import Listclass Solution: def searchInsert(self, nums: List[int], target: int) -> int: left = -1 # 左边界初始化为-1(超出数组范围) right = len(nums) # 右边界初始化为数组长度 while left + 1 < right: # 当left和right相邻时退出循环 m = (left + right) // 2 # 中间位置 if nums[m] >= target: # 中间值 >= 目标值,收缩右边界 right = m else: # 中间值 < 目标值,收缩左边界 left = m # 循环结束后,right即为插入位置 return right
核心特点
- 循环条件:
left + 1 < right
(左右边界不相邻),当left
和right
相邻时退出循环。 - 边界设置:
- 左边界
left = -1
(超出数组左边界,确保覆盖所有可能的左侧位置)。 - 右边界
right = len(nums)
(覆盖所有可能的右侧位置)。
- 左边界
- 收缩逻辑:
- 若
nums[m] >= target
:右边界收缩至m
(不跳过m
,因为可能是目标位置)。 - 若
nums[m] < target
:左边界收缩至m
(不跳过m
,循环条件保证最终收敛)。
- 若
- 返回值:
right
,此时left
和right
相邻,right
是第一个满足nums[right] >= target
的位置。
三种解法对比分析
left <= right
)left < right
)left + 1 < right
)left <= right
(left > right
退出)left < right
(left == right
退出)left + 1 < right
(相邻时退出)left=0, right=len(nums)-1
left=0, right=len(nums)
left=-1, right=len(nums)
m
(left/right = m ± 1
)m
(right = m
)m
(left/right = m
)left
left
(left == right
)right
left
可等于 len(nums)
)right
初始为 len(nums)
)正确性验证(典型案例)
nums = [1,3,5,6], target = 5
nums = [1,3,5,6], target = 2
nums = [1,3,5,6], target = 7
nums = [1,3,5,6], target = 0
nums = [], target = 0
总结
三种解法均通过二分查找实现 O(log n)
时间复杂度,核心目标是找到“第一个大于等于目标值的位置”,即插入位置。差异主要在于边界处理和循环条件:
- 解法一:经典二分查找逻辑,循环条件
left <= right
,适合入门理解,需注意循环结束后left
的含义。 - 解法二:通过
left < right
简化循环条件,右边界初始为len(nums)
,天然支持越界情况,工程中常用。 - 解法三:开区间处理方式,边界范围覆盖所有可能位置,逻辑严谨,适合复杂边界场景。
三种方法均能正确解决问题,选择取决于个人对二分查找边界的理解习惯。核心是理解“插入位置”的本质——数组中第一个大于等于目标值的位置,这也是二分查找“下界”的典型应用。