2022年5月更新
之前写的代码不是很好,参考我写的下面这几题的解答,有机会重写一下。
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
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
n=len(nums)
if n==0:
return 0
left, right = 0, n-1
while left <= right:
mid=left+ (right-left)//2
if nums[mid]==target:
return mid
elif nums[mid]>target:
right=mid-1
else:
left=mid+1
return left
leetcode 33中等. 搜索旋转排序矩阵
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
输入:nums = [1], target = 0
输出:-1
class Solution:
def search(self, nums: List[int], target: int) -> int:
def bs(nums,target):
l, r =0, len(nums)-1
while l<=r:
mid=l+(r-l)//2
if nums[mid]==target:
return mid
elif nums[mid]>target:
r=mid-1
else:
l=mid+1
print(l, target, nums)
if l>=len(nums):
return -1
elif target<nums[l] :
return -1
else:
return l
n=len(nums)
if n==0:
return -1
max_index=nums.index(max(nums))
ori_nums_1=nums[:max_index+1] #bigger
ori_nums_2=nums[max_index+1:] # smaller
index_1=bs(ori_nums_1,target)
index_2=bs(ori_nums_2,target)
# print(index_1 , index_2)
if index_1==-1 and index_2==-1:
return -1
if index_1!=-1:
return index_1
if index_2!=-1:
return index_2+len(ori_nums_1)
leetcode 34中等. 在排序数组中查找元素的第一个和最后一个位置
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
n=len(nums)
if n==0:
return [-1,-1]
l, r = 0, n-1
while l<r: # 如果l=r=mid,那么因为相同元素存在,循环可能不会结束
mid=l+(r-l)//2
if nums[mid]<target:
l=mid+1
else: #也就是说当target等于nums[mid]的时候
#最右边会变成target大小,然后left不断右移,直到第一次left>=right
#而此时的left,就是target大小的元素第一次出现的位置
r=mid
print(l)
first_index=r if nums[r]==target else -1
return [first_index, last_index]
l , r = 0, n-1
while l<r:
mid=l+(r-l+1)//2 # 保证l偏向右边
if nums[mid]>target:
r=mid-1
else:
l=mid
print(l)
last_index=l if nums[l]==target else -1
-------------------旧版文章--------------------------------
二分查找使用有两个条件
- 表必须有序(增或减,可包含重复值)
- 表必须为顺序存储结构【数据结构可以分成栈,队列,线性表;线性表下有顺序存储结构和链式存储结构】
用起来的时候情况很多,但是都可以根据一个基本架构来稍微改变条件
题目0,返回target第一次出现的位置,注意target必然在nums中。
def bs0(nums,target):
l,r=0,len(nums)-1
while(l<r):
mid=l+((r-l)>>1)
# >>1是除以二的操作,结果向下取整,7>>1=3
#写成(l+r)/2可能会溢出
if(target<=nums[mid]):
#将right左移 进而mid左移 匹配第一次出现的
r=mid
else:
l=mid+1
return l
先来理解一下这段代码,要得到第一次出现的位置,最终返回的必然是left的值,我们不关心target最后出现的位置。所以当target<=nums[mid]时候,我们就可以将right左移到mid处。
这局代码使得我们不关心
首先看这一段代码,它的输出是:
第一个等于target的元素值的index(存在元素值等于target),比如
[1,1,2,3,4,4,5],target=2; 输出是2
第一个大于target的元素值的index(不存在元素值等于target),比如
[1,1,2,2,4,4,5],target=3; 输出是4
[1,1,2,2,4,4,5],target=6; 输出是7
但是这里index=7是不存在的,一般会要求返回-1,所以代码变成了
def bs1(nums,target):
left,right=0,len(nums)-1
while(left<right):
mid=left+((right-left)>>1) #话说之前这里掉了个括号。。。
if(target<=nums[mid]):
right=mid
else:
left=mid+1
return left if left<len(nums) else -1
所以看进一步的要求
1. 要求找到target第一次出现的下标,如果target不存在于数组中,返回-1
如果是这种情况,就不是输出第一个大于target的元素的下标了。而是在找不到相等值的时候,直接输出-1;但是需要注意left的值大于等于len(nums)的情况,这种情况下nums[left]直接报错index out range
def bs2(nums,target):
left,right=0,len(nums)-1
while(left<right):
mid=left+((right-left)>>1) #话说之前这里掉了个括号。。。
if(target<=nums[mid]):
right=mid
else:
left=mid+1
if left<len(nums):
return left if nums[left]==target else -1
else:
return -1
这种情况下,[1,1,2,2,4,4,5],target=3; 输出是-1
2. 元素最后一次出现的下标(最大下标),不存在则返回-1
之前注释中说过判断条件target<=nums[mid]
的作用是将right左移,进而mid左移,优先找先出现的元素;现在如果找最大下标,那就是需要将left右移。
def bs3(nums,target):
left,right=0,len(nums)-1
while(left<right):
mid=left+((right-left)>>1)
if(nums[mid]<=target):
left=mid
else:
right=mid-1
if right<len(nums):
return right if nums[right]==target else -1
else:
return -1
进一步,可以统计某一个数出现的次数,即用最后一次出现的位置减去第一次出现的位置然后加一,调用上述两个函数就好,具体代码不写了。
备注
lintcode第十四题二分法,要求找第一次出现的位置,否则返回-1
def bs(nums,target):
left,right=0,len(nums)-1
while(left<right):
mid=left+((right-left)>>1) #话说之前这里掉了个括号。。。
if(target<=nums[mid]):
right=mid
else:
left=mid+1
return left if (nums[left]==target and left<len(nums)-1)else -1
当测试数据为[0,1,2,3,4,5,6]
target=6 的时候: 上述代码返回是-1,但是期望答案是6
target=7 的时候:上述代码返回是index out of range,但是期望答案是-1
然而lintcode还accepted了.
错误有两个:
第一个错误在于右边界条件,这段代码最后的条件处给的是left <len(nums)-1
,这样会漏掉最后一个元素,正确的解法是上面的bs2函数,最后的判断应left<len(nums)
第二个错误是没有考虑到left大于等于len(nums)的情况,这种情况下nums[left]直接报错index out range
正确的写法应该是bs2中的写法