二分法
每次将搜索范围缩小一半,可以在O(log n)的时间找到正确答案。
二分法的前提条件:单调性
核心:利用单调性调整二分查找的区间
二分法步骤:
1、候选区间[left,right]
2、不断循环,直至区间满足特定条件
—2.1、计算中心点mid = (left + right) / 2
—2.2、判断中点是否合法,根据中点的计算结果调整[left,right]
二分法用途:
1、查找有序列表a中元素x的下标
2、浮点二分
3、二分答案
二分法题型
浮点二分
l , r = 1 , 2
eps = 1e-4
#当区间长度大于eps,说明前3位小数不同
#否则,前三位小数已确定
while r - l >= eps:
print("[{},{}]".format(l,r))
mid = (l + r) / 2
#若mid²>2下次为[l,mid],否则[mid,r]
if mid ** 2 > 2:
r = mid
else:
l = mid
运行结果
[1,2]
[1,1.5]
[1.25,1.5]
[1.375,1.5]
[1.375,1.4375]
[1.40625,1.4375]
[1.40625,1.421875]
[1.4140625,1.421875]
[1.4140625,1.41796875]
[1.4140625,1.416015625]
[1.4140625,1.4150390625]
[1.4140625,1.41455078125]
[1.4140625,1.414306640625]
[1.4141845703125,1.414306640625]
二分答案
描述
题目所求答案(一般为整数)具有单调性质,采用猜答案+二分
1、确定初始范围[left,right]
2、当left<=right时:
- mid = (left+right)//2
- check(mid),判断mid是否合法:定义check函数,什么时候合法根据题目单调性来确定,如果合法,更新ans = mid
- 根据合法调整左右区间:如left = mid+1 或right = mid-1
二分答案模板
#二分答案
def check(x):#判断是否合法,合法返回True,否则False
pass
l , r , ans = 0#初始化
while l <= r:
mid = (l + r) // 2
if check(mid):
ans = mid
l = mid + 1
else:
r = mid -1
print(ans)
99分巧克力
题目链接:99分巧克力
假设边长是x,初始区间[1,100000]
什么是合法?边长为x时,能否切出k块
如果mid合法,要求最大值,下一次为[mid+1,right],否则下次为[left,mid-1]
n , k = map(int,input().split())
a = []
for i in range(n):
x , y = map(int,input().split())
a.append((x,y))
def check(x):
cnt = 0
for h ,w in a :
cnt += (h//x)*(w//x)
return cnt>=k
l , r = 1 , 100000
ans = 1
while l <= r:
mid = (l + r) // 2
if check(mid):
ans = mid
l = mid +1
else:
r = mid -1
print(ans)
运行结果
2 10
6 5
5 6
2
364跳石头
题目链接:364跳石头
“最大值最小化”、“最小值最大化”,一般用二分答案
假设“最短跳跃距离”为x:
- 是否有单调性
- 什么叫做合法
- 合法后如何调整区间
L, N, M = map(int, input().split()) # 输入长度为L的直线,岩石数量为N,最多移除数量为M
D = [] # 存储岩石的位置
for i in range(N):
D.append(int(input())) # 输入每个岩石的位置
def check(x): # 判断当最短跳跃距离为x时,移走的岩石数量是否不超过M个
now_idx = 0 # 当前坐标初始化为0
cnt = 0 # 移除数量初始化为0
for i in range(N): # 遍历岩石位置
if D[i] - now_idx < x: # 如果当前岩石与前一个岩石之间的距离小于x,则需要移除这块岩石
cnt += 1 # 移除数量加1
else: # 否则,更新当前坐标now_idx为当前岩石的位置
now_idx = D[i]
if L - now_idx < x: # 判断最后一个岩石跳到终点的距离是否小于x
return False # 如果是,则返回False
return cnt <= M # 如果移除数量cnt小于等于M,则返回True,否则返回False
left, right, ans = 1, L, 1 # 二分查找区间为[1, L],初始答案为1
while left <= right: # 当左边界小于等于右边界时,继续查找
mid = (left + right) // 2 # 计算中间值mid
if check(mid): # 如果当最短跳跃距离为mid时,移走的岩石数量不超过M个
ans = mid # 更新答案为mid
left = mid + 1 # 将左边界更新为mid+1
else:
right = mid - 1 # 否则,将右边界更新为mid-1
print(ans) # 输出最短跳跃距离x的最大值
运行答案
25 5 2
2
11
14
17
21
4
3404肖恩的乘法表
题目链接:3404肖恩的乘法表
n, m, k = map(int, input().split()) # 输入三个整数 n、m、k
left, right = 1, n * m # 初始化左右边界
def check(x): # 定义一个函数,用于计算小于等于 x 的元素个数
cnt = 0
for i in range(1, n + 1): # 遍历行数
cnt += min(m, x // i) # 计算当前行中小于等于 x 的元素个数,并累加到 cnt 中
return cnt
ans = 0 # 初始化答案为 0
while left <= right: # 二分查找
mid = (left + right) // 2 # 计算中间值
if check(mid) >= k: # 如果小于等于 mid 的元素个数大于等于 k
ans = mid # 更新答案
right = mid - 1 # 缩小右边界
else:
left = mid + 1 # 扩大左边界
print(ans)
运行结果
2 4 5
4