力扣原题链接: Leetcode Q35
题目描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
题目要求:
必须使用时间复杂度为 O(log n) 的算法。
由于要求使用时间复杂度为O(log n) 的算法,那么自然而然我选择了二分法来解决这个问题。
重要题目提示:
nums 为 无重复元素 的 升序 排列数组
“无重复元素”和“升序”是两个很重要的前提。
无重复元素:一旦有重复元素,那么使用二分查找法返回的元素下标可能并不是最后一个重复元素。
升序:说明数组是有序数组,这也是使用二分法的前提。数组升序的性质也提示我们要寻找第一个大于target的位置作为插入位置。如果是降序的话,就得另当别论。
我们知道在寻找target在数组的位置时,如果找不到target,那么将会返回-1。这道题便是针对找不到target的情况,不再返回-1,而是返回target 之后第一个 大于target的位置了。
Leetcode代码
- 左闭右闭情况 [left,right]
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left,right=0,len(nums)-1
while left<=right:
middle=left+(right-left)//2
if nums[middle]>target:
right=middle-1
elif nums[middle]<target:
left=middle+1
else: return middle
return left
手搓
target 0 应该插入0
1 round l0, r3 middle=0+3//2=1 --> l0, r1-1=0
2 round l0, r0 middle=0+(0)//2=0 --> l0, r=0-1=-1
跳出循环 return l
target 4 应该插入2
1 round l0, r3 middle=0+3//2=1 --> l=1+1=2, r3
2 round l2, r3 middle=2+(3-2)//2=2 --> l2, r=2-1=1
跳出循环 return l
target 7 应该插入4
1 round l0, r3 middle=0+3//2=1 --> l=1+1=2, r3
2 round l2, r3 middle=2+(3-2)//2=2 --> l=2+1=3, r3
3 round l3, r3 middle=3+(3-3)//2=3 --> l=3+1=4, r3
跳出循环 return l
可以自己手搓循环… …
我们能发现最后一次循环,left等于middle,但不一定等于right。
那为什么返回left?
因为退出while循环后,left > right,此时有两种情况:
- 一种是middle指向的值大于target(此时middle是第一个大于target的下标),此时right=middle-1导致了left大于right,而target要插入的位置是middle的位置,即left,因为left和middle相等
- 另一种是middle指向的值小于target,此时left=middle+1导致了left大于right,而target要插入的位置是middle+1的位置,即left
- 因此退出循环时,left指向的就是第一个大于target的位置,right指向的就是最后一个小于target的位置
- 左闭右开情况 [left,right)
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left,right=0,len(nums)
while left<right:
middle=left+(right-left)//2
if nums[middle]>target:
right=middle
elif nums[middle]<target:
left=middle+1
else: return middle
return left
左闭右开依旧返回left,而不用考虑+1还是-1的问题。
总结
简单地说,由于数组升序的性质,我们找的一定得是第一个大于target的位置。随着while循环进行,left和right会不断的收缩,一直假设target在left和right中间, 到最后退出循环,也没找到,那么这个时候left指向的就是第一个大于target的位置,考虑到left在区间内始终是能取到的数值(不受开闭影响,始终是闭),left就一定是target插入的位置。