二分查找:给定一个有序数组,需要查找目标值的位置。
思路,每次比较目标值与数组中间位置值,根据比较结果来返回目标值位置(nums[mid]==target),或者改变数组的左边界、右边界,或者返回-1(数组中不存在target)。
代码如下:
class Solution:
def search(self,nums:list[int],target:int)->int:
#初始化左右边界
left,right=0,len(nums)-1
while left<=right:
#每次循环取区间的中点
mid=(left+right)//2
#如果恰好等于则返回下标
if nums[mid]==target:
return mid
#根据结果改编查找的区间
elif nums[mid]<target:
left=mid+1
else:
right=mid-1
#未搜索到target
return -1
理解了基础的思路之后,在使用中需要考虑更多的细节:
1、区间的开闭 2、mid 如何取值 3、出界条件 4、搜索区间如何变化
1、区间 (l为左边界,r为右边界)
左闭右闭:初始化l=0,r=len(nums)-1
左闭右开:初始化l=0,r=len(nums)
在写的时候左闭右开的写法不可以取到右边界上的点,左闭右闭则可以取到两个边界点,建议使用左闭右闭。
2、mid取值
mid=(l+r)//2 -(1)
mid=(l+r+1)//2 -(2)
mid=l+(r-l)//2 -(3)
mid=l+(r-l+1)//2 -(4)
先说第一第二个写法,元素个数是奇数时没有区别,他们的区别是个数为偶数时,(1)会取到中间靠左的元素,(2)取到中间靠右的元素;
(3)、(4)是与(1)、(2)对应的写法,可以防止整型溢出(py中没有)
3、出界条件
l<=r -(5)
l<r -(6)
条件为(5)时,循环结束l=r+1,也就遍历了数组的所有区间。使用条件(6)的话需要将return改为 return l if nums[l] == target else -1
4、搜索区间:
l=mid+1,r=mid-1 -(7)
l=mid+1,r=mid -(8)
l=mid,r=mid-1 -(9)
没太懂,但是好像(7)够用了
话不多说,上题目
704、二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
l,r=0,len(nums)-1
while(l<=r):
m=(l+r)//2
if nums[m]==target:
return m
elif nums[m]<target:
l=m+1
else:
r=m-1
return -1
35、搜索插入位置
与上一题相似,如果数组中没有target值的话需要返回插入位置,循环条件是l<=r,因此数组中没有target值的时候l=r+1正好是插入位置,return l 即可
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
l,r=0,len(nums)-1
while(l<=r):
m=(l+r)//2
if nums[m]==target:
return m
elif nums[m]<target:
l=m+1
else:
r=m-1
return l
374、猜数字大小
class Solution:
def guessNumber(self, n: int) -> int:
l,r=1,n
while(l<=r):
m=(l+r)//2
if (guess(m)==0):
return m
elif (guess(m)==1):
l=m+1
else:
r=m-1
return -1
69、x的平方根 输出m时需要添加条件(m+1)**2>x,确定x是在m^2,(m+1)^2之间
class Solution:
def mySqrt(self, x: int) -> int:
l,r=0,46340
while(l<=r):
m=(l+r)//2
m_2=m*m
if (m_2<=x and (m+1)**2>x):
return m
elif m_2<x:
l=m+1
else:
r=m-1
return -1
167、两数之和2 思路与暴力差不多,遍历数组的每一个元素,对于每一个元素,对后面的区间进行二分查找。
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
for i in range(len(numbers)-1):
left,right=i+1,len(numbers)-1
while left<=right:
#每次循环取区间的中点
mid=(left+right)//2
#如果恰好等于则返回下标,注意加一
if numbers[mid]+numbers[i]==target:
return [i+1,mid+1]
#根据结果改变查找的区间
elif numbers[mid]+numbers[i]<target:
left=mid+1
else:
right=mid-1
#未搜索到target
return -1
1011、在D天内送达包裹的能力
详解在注释中,主要是循环条件的写法;使用d和cur记录;变区间的条件与区间如何变化(这一点其实不太理解)
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
#二分查找,确定查找区间:目标重量在数组最大值和数组和之间
l = max(weights)
r = sum(weights)
#循环结束是l==r 是要输出的容量
while l < r:
#根据题目新加d,cur记录天数和当前的容量
d=1
cur=0
mid = (l +r) // 2
#对于数组中的数,如果相加后大于mid则将目前的weight留到下一天
for weight in weights:
if cur+weight>mid:
d+=1
cur=0
cur+=weight
#如果要求的天数days < d 这次循环对应的天数,则将重量变大,对应的天数d减小
#排除掉不符合的区间
if days<d:
l=mid+1
else:
r=mid
return l