4 二分搜索
二分查找的常用场景:寻找一个数、寻找左侧边界、寻找右侧边界。
先上框架:
def binary_search(nums,target):
"""非递归
对有序数组可以寻找下标
"""
if len(nums)==0:
return -1
#nums=sorted(nums)
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
return -1
binary_search([1,8],2)
def binary_search(nums,target):
"""递归"""
if not nums:
return False
#l,r = 0, len(nums)-1
nums=sorted(nums)
#print(sorted(nums))
mid = len(nums)//2
if nums[mid]==target:
return True
elif nums[mid]>target:
print(nums)
return binary_search(nums[:mid],target)
else:
print(nums)
return binary_search(nums[mid+1:],target)
binary_search([2,1,8],2)
def left_bound(nums,target):
"""非递归"""
if len(nums)==0:
return -1
l,r = 0, len(nums)-1
while l<=r:
mid = l + (r-l)//2
if nums[mid]==target:
#return mid
r = mid -1#因为要看左边界,所以先锁定右边界
elif nums[mid]>target:
r = mid-1
else:
l = mid +1
#检查L越界情况
if l>=len(nums):
return -1
if nums[l]!=target:
return -1
return l
left_bound([1,7,8,8],8)
def right_bound(nums,target):
"""右侧边界"""
if len(nums)==0:
return -1
l,r = 0, len(nums)-1
while l<=r:
mid = l + (r-l)//2
if nums[mid]==target:
#return mid
l = mid +1#因为要看左边界,所以先锁定右边界
elif nums[mid]>target:
r = mid-1
else:
l = mid +1
#检查L越界情况
if r<0:
return -1
if nums[l]!=target:
return -1
return r
right_bound([1,7,8,8],8)
704.二分查找(简单)
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
def binary_search(nums,target):
"""非递归
对有序数组可以寻找下标
"""
if len(nums)==0:
return -1
#nums=sorted(nums)
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
return -1
nums = [-1,0,3,5,9,12]
target = 9
binary_search(nums,target)
34.在排序数组中查找元素的第一个和最后一个位置(中等)
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
class left_right_bound(object):
def left_bound(self,nums,target):
"""左边界"""
if not nums:
return -1
l ,r =0 ,len(nums)-1
while l<=r:
mid = l + (r-l)//2
if nums[mid]>target:
r = mid -1
elif nums[mid]<target:
l = mid +1
else:
r = mid -1
if l>=len(nums):
return -1
if nums[l]!=target:
return -1
return l
def right_bound(self,nums,target):
"""右边界"""
if not nums:
return -1
l ,r =0 ,len(nums)-1
while l<=r:
mid = l + (r-l)//2
if nums[mid]>target:
r = mid -1
elif nums[mid]<target:
l = mid +1
else:
l = mid +1
if r<0:
return -1
if nums[l]!=target:
return -1
return r
def result(self,nums,target):
rb=self.right_bound(nums,target)
lb=self.left_bound(nums,target)
return [lb,rb]
if __name__=='__main__':
nums = [5,7,7,8,8,10]
target = 6
s=left_right_bound()
print(s.result(nums,target))
875.爱吃香蕉的珂珂(中等)
珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。
如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
示例 1:
输入: piles = [3,6,7,11], H = 8
输出: 4
示例 2:
输入: piles = [30,11,23,4,20], H = 5
输出: 30
示例 3:
输入: piles = [30,11,23,4,20], H = 6
输出: 23
提示: 1 <= piles.length <= 10^4
piles.length <= H <= 10^9
1 <= piles[i] <= 10^9
思路分析:这道题采用二分搜索。 首先我们将所有的堆按照升序排序,则最慢的速度left是将所有的时间花在最小的堆,最快的速度right是每个小时吃掉最大的堆的数量,我们每次取中值mid = (left + right) / 2,然后按照mid的这个吃香蕉的速度去计算吃掉所有堆的总时间,并且和H判断,然后更新[left, right]区间。
有一点需要注意一下,就是题干要求当我们吃完某个堆还剩余一段时间(不足一小时),我们不能转移到其他堆,只能停在当前堆。比如吃的速度为4,某个堆大小为6,第一个小时吃掉4根,第二个小时中吃掉剩余的2根,这时还剩余一段时间,只能停在此处。所有一共需要的时间 (堆的大小+吃香蕉的速度 - 1)/ 吃香蕉的速度或者(堆的大小 / 吃香蕉的速度) + (堆的大小 % 吃香蕉的速度 > 0)(这里的除法是int型除法)
import math
def minEatingSpeed(nums,t):
if len(nums)==1:
return (nums[0]+t-1)/t
#nums=sorted(nums)
l,r = 1,max(nums)
while l<r:
mid = l + (r-l)//2
cnt=0
for p in nums:
#cnt+= (p+mid-1)//mid #向上取整
cnt += math.ceil(p/mid)
if cnt > t:
l = mid+1
else:
r = mid
return l
piles =[ 30,11,23,4,20]
H = 5
minEatingSpeed(piles,H)
def minEatingSpeed( piles, H):
maxv = max(piles)
l = 1
r = maxv
while l < r:
m = (r - l) // 2 + l
cnt = 0
for p in piles:
cnt += (p + m - 1) // m
if cnt > H:
l = m + 1
else:
r = m
return r
piles = [30,11,23,4,20]
H = 6
minEatingSpeed(piles,H)
1011.在D天内送达包裹的能力(中等)
传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。
示例 1:
输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。
示例 2:
输入:weights = [3,2,2,4,1,4], D = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4
示例 3:
输入:weights = [1,2,3,1,1], D = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1
提示:
1 <= D <= weights.length <= 50000
1 <= weights[i] <= 500
def shipWithinDays(nums,t):
if len(nums)==1:
return (nums[0]+t-1)/t
#nums=sorted(nums)
r = sum(nums)
l = max(r//t, max(nums))
while l<r:
mid = l + (r-l)//2
cnt=0
cur =0
for p in nums:
if cur+p>mid:
cur =0
cnt += 1
cur += p
if cnt > t:
l = mid+1
else:
r = mid
return l
weights = [1,2,3,1,1]
D = 4
shipWithinDays(weights,D)
class Solution:
def shipWithinDays(self, weights, D):
r = sum(weights)
l = max(r//D, max(weights))
while l <= r:
m, need, cur = (l + r)//2, 1, 0
for w in weights:
if cur + w > m:
need += 1
cur = 0
cur += w
if need > D:
l = m + 1
else:
r = m - 1
return l
weights = [1,2,3,1,1]
D = 4
s=Solution()
s.shipWithinDays(weights,D)