python 查找目标值在排序数组中的位置或插入点 (二分法-简单)含源码(十)
问题说明(含示例)
问题描述:给定一个升序排序的数组 nums 和一个目标值 target,在数组中找到目标值并返回其索引。若目标值不存在于数组中,返回它按顺序插入的位置。要求算法的时间复杂度必须为 O(log n)。
示例:
-
输入:
nums = [1,3,5,6],target = 5输出:2解释:目标值 5 存在于数组中,位于索引 2 处。 -
输入:
nums = [1,3,5,6],target = 2输出:1解释:目标值 2 不存在于数组中,按升序应插入在 1 和 3 之间,对应索引 1。 -
输入:
nums = [1,3,5,6],target = 7输出:4解释:目标值 7 大于数组中所有元素,应插入在数组末尾,对应索引 4(数组长度为 4)。
解题关键
由于数组是升序排序且要求 O(log n) 时间复杂度,最优解法是二分查找。核心思路是通过不断缩小搜索范围,定位目标值的位置或插入点:
- 初始化指针:
left = 0(左边界),right = len(nums) - 1(右边界)。 - 二分循环:当
left <= right时,计算中间索引mid = (left + right) // 2,并比较nums[mid]与target:- 若
nums[mid] == target:找到目标值,直接返回mid; - 若
nums[mid] < target:目标值在右侧,更新left = mid + 1; - 若
nums[mid] > target:目标值在左侧,更新right = mid - 1。
- 若
- 确定插入位置:当循环结束(
left > right)时,left即为目标值的插入位置(此时left是第一个大于target的元素索引,或数组长度)。
对应代码
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
# 计算中间索引(避免(left + right)溢出,等价于(left + right) // 2)
mid = left + (right - left) // 2
if nums[mid] == target:
return mid # 找到目标值,返回索引
elif nums[mid] < target:
left = mid + 1 # 目标在右侧,移动左边界
else:
right = mid - 1 # 目标在左侧,移动右边界
# 循环结束时,left即为插入位置
return left
对应的基础知识
实现该算法需掌握以下基础概念与操作:
-
二分查找的核心逻辑
- 利用数组的有序性,通过比较中间元素与目标值,每次将搜索范围缩小一半(排除左半部分或右半部分),实现高效查找。
- 循环条件
left <= right确保所有可能的位置都被检查(包括left == right时的单个元素)。
-
指针更新规则
- 当
nums[mid] < target时,目标值一定在mid右侧,故left = mid + 1(跳过mid及左侧); - 当
nums[mid] > target时,目标值一定在mid左侧,故right = mid - 1(跳过mid及右侧)。
- 当
-
插入位置的确定
- 循环结束时,
left必然大于right,此时left的位置满足:所有nums[0:left-1] < target且nums[left:end] > target(若left等于数组长度,则 target 大于所有元素),因此left是插入的正确位置。
- 循环结束时,
-
中间索引计算
- 用
mid = left + (right - left) // 2而非(left + right) // 2,避免left + right数值过大导致的整数溢出(在 Python 中无溢出风险,但这是通用的安全写法)。
- 用
对应的进阶知识
该问题的解决涉及二分查找的深层逻辑与复杂度分析:
-
时间复杂度分析
- 每次循环将搜索范围从
n缩小到n/2,经过log2(n)次循环后范围缩小至 1,故时间复杂度为O(log n),满足题目要求。 - 相比线性查找(
O(n)),二分查找在大规模数组(如n = 10^6)时效率提升显著(log2(10^6) ≈ 20步)。
- 每次循环将搜索范围从
-
二分查找的适用场景
- 必须是有序数组(升序或降序,本题为升序);
- 支持随机访问(如数组,可通过索引直接访问中间元素;链表不适用,因访问中间元素需
O(n)时间)。
-
边界情况的隐性处理
- 目标值小于所有元素(如
nums = [2,3,4],target = 1):循环结束后left = 0,正确返回插入位置 0; - 目标值大于所有元素(如示例 3):循环结束后
left = len(nums),正确返回数组长度; - 数组为空(
nums = []):初始right = -1,循环不执行,直接返回left = 0,符合逻辑。
- 目标值小于所有元素(如
-
与线性查找的对比
- 线性查找(
for i in range(len(nums)): ...)时间复杂度O(n),适用于小规模数组; - 二分查找
O(log n)适用于大规模数组,是有序数组查找的最优策略,但需额外维护数组的有序性(插入 / 删除操作可能破坏有序性)。
- 线性查找(
编程思维与启示
-
“有序性” 的充分利用:面对有序数据,优先考虑二分查找而非暴力遍历,这是降低时间复杂度的关键(从
O(n)到O(log n))。 -
“边界控制” 的精确性:二分查找的难点在于指针更新和循环终止条件的设计,
left <= right与left = mid + 1/right = mid - 1的配合,确保不遗漏任何可能位置,同时避免死循环。 -
“结果复用” 的巧思:循环结束后直接返回
left而非额外计算插入位置,体现了对二分查找过程的深刻理解 —— 查找失败时的指针状态已隐含插入位置信息,无需冗余逻辑。 -
“通用写法” 的严谨性:中间索引用
left + (right - left) // 2计算,虽在 Python 中无溢出问题,但这种写法适用于所有编程语言,体现了跨语言的代码通用性思维。
更多推荐
所有评论(0)