278、第一个错误版本
有n个版本,第一个错误版本和之后的所有错误版本调用isbadversion都会返回true 之前的版本返回flase 。需要找到第一个错误版本
很自然地想到二分查找,不然就是遍历数组从前往后查。
在本题需要注意的是,如果m是错误版本,则需要变更右边界,但是当前的m可能就是答案,所有不能r=m+1,需要写成r=m。而左边界则因为排除掉m了,l=m+1。
循环条件l<r,因此结束循环是l==r,直接输入l。
class Solution:
def firstBadVersion(self, n: int) -> int:
l,r=1,n
while(l<r):
m=(l+r)//2
flag=isBadVersion(m)
if not flag:
l=m+1
elif flag:
r=m
return l
33、搜索旋转排序数组
有个数组nums 原本是升序,但是有k+1一个移到了数组前面,因此数组被分为两个有序数组[0,k]和[k+1,len(nums)-1],分别使用二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
k=0
#寻找旋转了多少个数,k
for i in range (len(nums)-1):
if nums[i]>nums[i+1]:
k=i
break
def binarysearch(l,r,nums,target):
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
ans1=binarysearch(0,k,nums,target)
ans2=binarysearch(k+1,len(nums)-1,nums,target)
if ans1!=-1:
return ans1
elif ans2!=-1:
return ans2
else:
return -1
153、154寻找旋转排序数组中的最小数
思路都一样,题目的区别是154有重复的数。直接找出两个数组的位置,返回最小值即可.
使用二分法的思路见官方详解。
class Solution:
def findMin(self, nums: List[int]) -> int:
k=0
#寻找旋转了多少个数,k
for i in range (len(nums)-1):
if nums[i]>nums[i+1]:
k=i
break
def findmin(l,r,nums):
if l<=len(nums)-1:
return nums[l]
else:
return 999999
ans1=findmin(0,k,nums)
ans2=findmin(k+1,len(nums)-1,nums)
return min(ans1,ans2)
4、寻找两个正序数组的中位数
有两个正序数组,需要找出它们合并之后的中位数,时间要求O(log(m+n))。看到这个要求可以想到二分或者是堆。
接下来分析如何进行二分查找:找出中位数即找出数组中间的一个或两个数。当长度为奇数时是找出第(m+n+1)//2个数,为偶数是找出第(m+n)//2和(m+n)//2+1,求平均值。分别使用索引idx1和idx2划分nums1和nums2,使得数组的左侧有(m+n+1)//2个数——条件1,以及左侧的数均小于右侧的数——条件二
因此遍历区间是[0,m]
变化条件是 条件二左侧的数不是均小于右侧的数:
nums1[idx1]<nums2[idx2-1]:
nums1[idx1-1]>nums2[idx2]
当满足条件一和条件二之后跳出循环,返回值。需要注意边界条件和数组总长度是否为偶数。
如果为奇数,只需要返回maxleft,此时不考虑数组越界(边界)的话,直接比较nums[idx1-1]、nums[idx2-1]的大小。但是需要考虑idx1或idx2为0,也就是nums全在中位数的右侧的情况。
如果为奇数,还需要考虑中位数右侧的最小值,同样地需要考虑边界条件。又因为一开始做了交换,使得nums1永远是长度较短的数组,所以代码中不需要考虑idx2==n。
如果有不清楚的地方还可以参考哔站视频:讲得非常简单易懂,而且也有代码讲解LeetCode004-两个有序数组的中位数-最优算法代码讲解_哔哩哔哩_bilibili
最后代码如下:
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m,n=len(nums1),len(nums2)
if m>n :
nums1,nums2=nums2,nums1
m,n=n,m
l,r=0,m
while l<=r:
#保证条件1
idx1=(l+r)//2
idx2=(m+n+1)//2-idx1
#保证条件2
if idx1!=m and idx2!=0 and nums1[idx1]<nums2[idx2-1]:
l=idx1+1
elif idx1!=0 and nums1[idx1-1]>nums2[idx2]:
r=idx1-1
#条件都满足退出循环
else:
break
#考虑边界
if idx1==0:
maxleft=nums2[idx2-1]
elif idx2==0:
maxleft=nums1[idx1-1]
else:
maxleft=max(nums2[idx2-1],nums1[idx1-1])
#长度为奇数
if (m+n)%2==1:
return maxleft
#长度为偶数
else:
if idx1==m:
minright=nums2[idx2]
elif idx2==n:
minright=nums1[idx1]
else:
minright=min(nums1[idx1],nums2[idx2])
return (maxleft+minright)/2.0
875、爱吃香蕉的珂珂
与前面D天运送包裹的题目类似,先确定遍历区间每小时最少吃多少香蕉,和最多吃多少香蕉。
然后要注意的是向上取整的写法:
hour += (num + mid - 1) // mid # 等价于向上取整
一开始我写的是hour+=num//mid+1,忽略了num是mid的整数倍的情况
class Solution:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
#确定区间
l=1
r=max(piles)
while l<=r:
hour=0
mid=(l+r)//2
for num in piles:
if num<=mid:
hour+=1
else:
hour=hour+(num+mid-1)//mid
if hour>h:
l=mid+1
else:
r=mid-1
return l
410、分割最大数组
给定一个数组,需要将数组分为k个非空的连续子数组,使得这k个子数组和的最大值最小。
比如nums=[7,2,5,10,8],k=2 应该分成[7,2,5]、[10,8] 输出18
如何使用二分法划分:
确定划分区间,也就是子数组和的最小值和最大值,其实和之前的题差不多
l = max(nums) # 最小可能的最大子数组和(至少为数组中的最大值)
r = sum(nums) # 最大可能的最大子数组和(即不分割时的总和)
进行比较变更搜索区间
感觉这题的难点可能在这,一开始做的时候我想先划分符合要求的子数组,也就是划分成k个,比较子数组的和的最大值和mid然后进行区间变化。但是这样子想复杂了。应该直接用mid限制子数组的最大值,进行划分,划分后直接比较子数组数count和要求的数量k。
class Solution:
def splitArray(self, nums: list[int], k: int) -> int:
# 确定二分查找的区间
l = max(nums) # 最小可能的最大子数组和(至少为数组中的最大值)
r = sum(nums) # 最大可能的最大子数组和(即不分割时的总和)
# 二分查找
while l < r:
mid = (l + r) // 2 # 当前尝试的最大子数组和
count = 1 # 当前分割的子数组数量
current_sum = 0 # 当前子数组的和
# 模拟分割过程
for num in nums:
if current_sum + num > mid: # 当前子数组的和超过 mid,开始新的子数组
count += 1
current_sum = num
else:
current_sum += num
# 根据分割结果调整搜索范围
if count <= k:
r = mid # 分割成功,尝试更小的最大子数组和
else:
l = mid + 1 # 分割失败,尝试更大的最大子数组和
# 返回最小满足条件的最大子数组和
return l