例题 1:浮点二分------计算 √2
| 项目 | 内容 |
|---|---|
| 来源 | 蓝桥云课基础模板 |
| 类型 | 浮点二分 |
| 核心 | 精度控制、区间收缩 |
题目描述
计算 √2,保留 3 位小数。利用 x² 在 x > 0 时的单调递增性。
输入输出
无输入,输出 1.414
题解
浮点二分与整数二分的区别:
- 终止条件 :
right - left >= eps(区间长度) - eps 选取 :保留
n位小数,eps = 1e-(n+1) - 不判相等:浮点数不存在精确相等
python
left, right = 1.0, 2.0
eps = 1e-4 # 保留3位小数,精度取1e-4
while right - left >= eps:
mid = (left + right) / 2.0
if mid * mid > 2:
right = mid # 中点太大,答案在左半区间
else:
left = mid # 中点太小或正好,答案在右半区间
print(f"{left:.3f}")
推演过程
| 轮次 | 区间 | mid | mid² | 判断 |
|---|---|---|---|---|
| 1 | [1.0000, 2.0000] | 1.5000 | 2.2500 > 2 | right = 1.5000 |
| 2 | [1.0000, 1.5000] | 1.2500 | 1.5625 < 2 | left = 1.2500 |
| 3 | [1.2500, 1.5000] | 1.3750 | 1.8906 < 2 | left = 1.3750 |
| 4 | [1.3750, 1.5000] | 1.4375 | 2.0664 > 2 | right = 1.4375 |
| 5 | [1.3750, 1.4375] | 1.4062 | 1.9775 < 2 | left = 1.4062 |
| 6 | [1.4062, 1.4375] | 1.4219 | 2.0217 > 2 | right = 1.4219 |
| 7 | [1.4062, 1.4219] | 1.4141 | 1.9996 < 2 | left = 1.4141 |
区间长度 1.4219 - 1.4141 = 0.0078 < 0.0001?不对,继续到长度 < eps:
最终 left ≈ 1.4142,输出 1.414
关键细节
| 坑点 | 说明 |
|---|---|
eps 太小 |
循环次数爆炸,1e-6足够,不要1e-10 |
mid*mid == 2 |
浮点不能用 ==,永远走 > 或 <= 分支 |
| 固定循环次数 | for _ in range(100) 也可,更稳定 |
例题 2:二分答案------巧克力切割(蓝桥杯 P99)
| 项目 | 内容 |
|---|---|
| 链接 | https://www.lanqiao.cn/problems/99/learning/ |
| 类型 | 二分答案(最大值) |
| 时间限制 | 1s |
| 空间限制 | 256MB |
题目描述
N 块巧克力,H_i × W_i 的长方形。切出 K 块大小相同的正方形,求最大边长。
输入格式
N K
H1 W1
H2 W2
...
HN WN
输出格式
一个整数,最大边长。
样例
输入:
2 5
6 5
5 5
输出:
2
题解
单调性分析 :边长 x 越大,每块巧克力切出的块数越少,总块数越少。具有单调性。
check(x) :边长为 x 时,能否切出至少 K 块?
- 第
i块:(H_i // x) × (W_i // x)块 - 总和
>= K则合法
二分策略 :求最大值 → check(mid) 合法时,left = mid + 1 尝试更大
python
n,k = map(int,input().split())
a = []
for _ in range(n):
h,w = map(int,input().split())
a.append((h,w))
# 假定切出的巧克力边长x,能否切出k快
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)
推演验证
巧克力:[6x5, 5x5],K=5
check(3): (6//3)*(5//3)=2*1=2, (5//3)*(5//3)=1*1=1, 总计3 < 5 → False
check(2): (6//2)*(5//2)=3*2=6, (5//2)*(5//2)=2*2=4, 总计10 >= 5 → True
check(3) False, check(2) True,尝试 check(2) 和 check(3) 之间无整数
答案 = 2 ✓
复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间 | O(N × log(max(H,W))) |
二分约17次,每次遍历N块 |
| 空间 | O(N) |
存储巧克力尺寸 |
同类对比
| 题目 | 区别 |
|---|---|
| 例题3(跳石头) | 求"最小值最大化",check用贪心 |
| LeetCode 875 | 求最小速度,check用累加时间 |
例题 3:二分答案------跳石头(蓝桥杯 P364)
| 项目 | 内容 |
|---|---|
| 链接 | https://www.lanqiao.cn/problems/364/learning/ |
| 类型 | 二分答案(最小值最大化) |
| 标签 | 贪心 + 二分 |
题目描述
河道长 L,起点到终点间有 N 块石头。至多移走 M 块,使得最短跳跃距离尽可能大。求这个最大值。
输入格式
L N M
D1
D2
...
DN
D_i:第 i 块石头与起点的距离。
输出格式
一个整数,最短跳跃距离的最大值。
样例
输入:
25 5 2
2
11
14
17
21
输出:
4
题解
题型识别:"最小值最大化" → 二分答案经典标志。
单调性 :如果最短跳跃距离 x 可行(移走不超过M块),则所有 <= x 的距离也可行?不对,应该是:如果 x 可行,则 x-1 一定可行 (更容易满足)。所以可行解具有单调性,二分找最大的可行 x。
check(x) :假设最短距离为 x,贪心判断需要移走多少块:
- 维护
now_idx:当前所在位置 - 遍历石头,如果
石头距离 - now_idx < x,移走这块石头 - 否则跳到这里,更新
now_idx - 最后检查终点距离
python
import os
import sys
# 请在此输入您的代码
l,n,m = map(int,input().split())
d = []
for _ in range(n):
d.append(int(input()))
# 最短跳跃距离x,移除岩石数量不超过m,则合法
def check(x):
# 移除岩石数量
cnt = 0
# 上一个位置到起点距离
last = 0
for i in range(n):
# 能跳
if d[i]-last>=x:
last=d[i]
# 太小,搬石头
else:
cnt+=1
# 最后一步特判
if (l-last)<x:
# 搬走这块石头,不能直接False
# return False
cnt+=1
return cnt<=m
left,right = 1,l
ans = -1
while left<=right:
mid = (left+right)//2
if check(mid):
ans = mid
left=mid+1
else:
right=mid-1
print(ans)
推演验证
L=25, stones=[2,11,14,17,21], M=2
check(4):
now=0, cnt=0
i=0: 2-0=2 < 4 → cnt=1, 移走
i=1: 11-0=11 >= 4 → now=11
i=2: 14-11=3 < 4 → cnt=2, 移走
i=3: 17-11=6 >= 4 → now=17
i=4: 21-17=4 >= 4 → now=21
终点: 25-21=4 >= 4 ✓
cnt=2 <= M=2 → True
check(5):
now=0, cnt=0
i=0: 2 < 5 → cnt=1
i=1: 11-0=11 >= 5 → now=11
i=2: 14-11=3 < 5 → cnt=2
i=3: 17-11=6 >= 5 → now=17
i=4: 21-17=4 < 5 → cnt=3 > M=2 → 但继续
终点: 25-17=8 >= 5
cnt=3 > M=2 → False
答案在4和5之间,最大可行=4 ✓
关键易错点
| 错误 | 正确 | 后果 |
|---|---|---|
| 忘记终点检查 | 循环后判断 L - now_idx < x |
最后一段距离可能不足 |
cnt < M 写错 |
移走最后一块需要 cnt < M |
边界WA |
| 贪心方向反了 | 从左到右,能不移就不移 | 不是最优解 |
例题 4:二分答案------第K大元素(蓝桥杯 P3404)
| 项目 | 内容 |
|---|---|
| 链接 | https://www.lanqiao.cn/problems/3404/learning/ |
| 类型 | 二分答案(第K小) |
| 标签 | 数学 + 二分 |
题目描述
n × m 乘法表,第 i 行第 j 列为 i × j。求第 k 大的元素。
输入格式
n m k
输出格式
一个整数。
样例
输入:
2 4 4
输出:
3
题解
题型识别 :"第K小/大" → 二分答案,统计 <= x 的个数。
check(x) :统计乘法表中有多少个元素 <= x
- 第
i行:i × j <= x→j <= x // i - 贡献:
min(m, x // i)个
二分策略 :找最小的 x,使得 count(<= x) >= k
python
import os
import sys
# 请在此输入您的代码
n,m,k = map(int,input().split())
# <=x的数字有多少个
def check(x):
cnt = 0
for i in range(1,n+1):
# i*j<=x -> j<=x//i
cnt += min(m,x//i)
return cnt
left,right = 1,n*m
ans = 0
while left<=right:
mid = (left+right)//2
# 第k小的数字mid,在原数组中小于等于mid的数字至少有k个
if check(mid) >= k:
ans = mid
right = mid-1
else:
left = mid+1
print(ans)
推演验证
n=2, m=4, k=4
乘法表:
1 2 3 4
2 4 6 8
排序:[1, 2, 2, 3, 4, 4, 6, 8],第4大=3
check(3):
i=1: min(4, 3//1=3) = 3
i=2: min(4, 3//2=1) = 1
cnt = 4 >= 4 → 合法,尝试更小
check(2):
i=1: min(4, 2) = 2
i=2: min(4, 1) = 1
cnt = 3 < 4 → 不合法
答案 = 3 ✓
复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间 | O(n × log(n×m)) |
n,m <= 5×10^5,约38次二分 |
| 空间 | O(1) |
不存矩阵,直接算 |
关键优化
| 优化 | 效果 |
|---|---|
i > x 时 x//i = 0 |
提前break,减少循环次数 |
min(m, x//i) |
防止 j 超过列数 m |
例题 5:二分查找------基础模板(LeetCode 704)
| 项目 | 内容 |
|---|---|
| 链接 | https://leetcode.cn/problems/binary-search/ |
| 类型 | 二分查找 |
| 难度 | 🟢 简单 |
题目描述
给定有序数组 nums 和目标值 target,返回索引,不存在返回 -1。
题解
最基础的二分,三种写法对比:
写法1:闭区间 [left, right]
python
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right: # 闭区间,left==right有意义
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1 # mid不是答案,从mid+1开始
else:
right = mid - 1 # mid不是答案,到mid-1结束
return -1
写法2:左闭右开 [left, right)
python
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) # right是开边界
while left < right: # left==right时区间为空
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid # mid可能是答案,但不能包含mid
return -1
三种写法对比
| 写法 | 初始化 | 循环条件 | left更新 |
right更新 |
适用场景 |
|---|---|---|---|---|---|
| 闭区间 | 0, n-1 |
left <= right |
mid + 1 |
mid - 1 |
找特定元素 |
| 左闭右开 | 0, n |
left < right |
mid + 1 |
mid |
找下界/插入位置 |
| 开区间 | -1, n |
left + 1 < right |
mid |
mid |
浮点二分 |
📊 今日刷题总结
| 题号 | 考点 | 二分类型 | 难度 | 核心技巧 |
|---|---|---|---|---|
| 1 | √2计算 | 浮点二分 | ⭐⭐ | eps精度控制 |
| 2 | 巧克力切割 | 二分答案(最大值) | ⭐⭐⭐ | check统计块数 |
| 3 | 跳石头 | 二分答案(最小值最大化) | ⭐⭐⭐⭐ | 贪心+终点检查 |
| 4 | 第K大元素 | 二分答案(第K小) | ⭐⭐⭐⭐ | 数学统计替代遍历 |
| 5 | 二分查找 | 基础模板 | ⭐⭐ | 三种区间写法 |
二分答案万能模板(背下来)
python
def binary_search():
left, right = 最小值, 最大值
ans = 初始值
while left <= right:
mid = (left + right) // 2
if check(mid): # mid满足条件
ans = mid # 记录答案
left = mid + 1 # 求最大:往右缩
# right = mid - 1 # 求最小:往左缩
else:
right = mid - 1 # 求最大:往左缩
# left = mid + 1 # 求最小:往右缩
return ans