基础知识:
二分法:每次将搜索范围缩小一半,可以在O(logn)时间内找到正确答案。
二分法的前提条件:单调性
核心:
- 利用单调性调整二分查找的区间
- 根据中点值来调整区间,减少一半的搜索范围
例如:手算根号值
步骤:
- 1、候选区间
- 2、不断循环,直至区间满足特定条件
- 2.1、计算中点
- 2.2、判断中点是否合法,根据中点的计算结果调整
用途:
- 查找有序列表a中元素x的下标:见语法部分的二分:bisect---数组二分查找算法
- 浮点二分
- 二分答案
近似根号代码:
left, right = 1, 2
#[1.0001,1.0002]
eps = 1e-4
while (right-left) >= eps:
mid = (left + right) / 2
if mid * mid > 2:
#[left,mid]
right = mid
else:
#[mid,right]
left = mid
print(left,right)
left, right = 1, 2
for _ in range(50):
mid = (left + right) / 2
if mid * mid > 2:
#[left,mid]
right = mid
else:
#[mid,right]
left = mid
print(left,right)
二分答案
题目所求答案(一般为整数)具有单调性质,采用猜答案+二分
1、确定初始范围
2、当时:
check(mid):判断mid是否合法:(定义check函数,什么时候是合法,根据题目的单调性来确定)
如果合法:更新
根据合法调整左右区间,调整策略为二选一:
- left=mid+1
- right=mid-1
最大值最小化和最小值最大化经常用到。
如果你要求的答案是最小值,你要看看左边是不是还有更小的合法值,反之就要去看看右边。
代码模板
#二分答案
def check(x):
#判断x是否合法,合法返回True,否则返回False
pass
left,right,ans = 初始化
while left<=right:
mid=(left+right)//2
if check(mid):
ans= mid
left = mid + 1 #二选一
else:
right= mid - 1 #二选一
print(ans)
例题
蓝桥99 分巧克力
import os
import sys
# 请在此输入您的代码
#边长越大,块数越少,边长越小,块数越多。
def check(x):
# 假设x边长合法
# 合法条件:边长为x能够切出k块
cnt = 0 # 能够切出的块数
for H, W in a:
cnt += (H//x) * (W//x)
return cnt >= K # 如果合法,返回True,否则,返回False
N, K = map(int, input().split())
a = []
for _ in range(N):
h, w = map(int, input().split())
a.append((h, w))
left, right, ans = 1, 100000, 1 # 可能的最小值,最大值,根据题意得,或者是数据范围。
while left<=right: # 在这个范围内猜答案
mid=(left + right) // 2
if check(mid):
ans= mid
left = mid + 1 #二选一
else:
right= mid - 1 #二选一 根据题意选
print(ans)
蓝桥364 跳石头
import os
import sys
# 请在此输入您的代码
def check(x):
# 移除的石头越多,最短的跳跃距离会变大
# 假设最短跳跃距离为x,移除石头不超过M即为合法
cnt = 0 # 表示移除的数量
last_idx = 0 # 表示上一个位置
for i in range(N):
if a[i] - last_idx >= x:
last_idx = a[i]
else:
cnt += 1 # 当两个石头之间的距离小于最小距离,要移除石头
if L - last_idx < x: # 最后一块石头的特判,因为最后一块不能移
return False
return cnt <= M
L, N, M = map(int, input().split())
a = []
for i in range(N):
a.append(int(input()))
left, right = 1, L
ans = -1
while left <= right: # 经典模板
mid = (left + right) // 2
if check(mid):
ans = mid
left = mid + 1 # 最小距离的最大值,说明要从mid右边找,是不是还有更大的合法值。
else:
right = mid - 1
print(ans)
蓝桥3404 肖恩的乘法表(第k大问题)
n, m, k = map(int, input().split())
def check(x):
cnt = 0
for i in range(1, n + 1):
# i * j >= x
cnt += min(x // i, m) # j 有限制,要在两者之中去最小值
return cnt
left, right = 1, n * m
ans = 0
while left <= right:
mid = (left + right) // 2
if check(mid) >= k: # 前面至少有k个数字比x元素小的 比如1 2 3 3 4 其实 3是第3大的,但是有重复值。
ans = mid
right = mid - 1 # 找最小值,在mid左区间找
else:
left = mid + 1
print(ans)
总结:
- 这题能不能用二分?首先,题目的变量要具有明显的单调性。
- 然后应该依据题目判断什么是合法条件。
- 判断完题目合法条件之后关注最终答案想要的是什么,是最小值?最大值?
- 写好合法判断函数后,利用模板猜答案,注意特殊情况特判。