【每日一题】二分算法

二分算法是算法竞赛中最基础也最重要的技巧之一,核心思想是每次将搜索范围缩小一半,在 O(log n) 时间内找到答案。


一、核心原理

1.1 二分的前提条件

单调性:如果答案具有单调性(满足条件的答案连续分布),就可以用二分。

1.2 三大类型对比

类型 应用场景 终止条件 核心代码特征
二分查找 在有序数组中找元素 left <= right 直接比较 mid 与目标值
浮点二分 求浮点数的精确值 right - left >= eps mid = (left + right) / 2
二分答案 求最优解(最大/最小值) left <= right 配合 check() 函数验证

二、浮点二分:计算 √2

2.1 题目描述

计算 √2,保留 3 位小数。利用 x > 0 时的单调性。

2.2 推导过程

步骤 区间 中点 中点² 判断
1 [1, 2] 1.5 2.25 > 2 太大,缩左
2 [1, 1.5] 1.25 1.5625 < 2 太小,缩右
3 [1.25, 1.5] 1.375 1.890625 < 2 太小,缩右
4 [1.375, 1.5] 1.4375 2.066 > 2 太大,缩左
5 [1.375, 1.4375] 1.40625 1.9775 < 2 太小,缩右
6 [1.40625, 1.4375] 1.421875 2.0217 > 2 太大,缩左
7 [1.40625, 1.421875] 1.4140625 1.9995 < 2 接近目标

2.3 完整代码

python 复制代码
left, right = 1, 2
eps = 1e-4  # 精度要求,保留3位小数需要1e-4

while right - left >= eps:
    mid = (left + right) / 2
    if mid * mid > 2:
        right = mid  # 中点太大,答案在左半区间
    else:
        left = mid   # 中点太小,答案在右半区间

print(f"{left:.3f}")  # 输出 1.414

2.4 关键细节

注意点 说明
eps 的选取 保留 n 位小数,eps = 1e-(n+1)
浮点数比较 不能用 ==,用区间长度判断
循环次数 固定 for _ in range(100) 也可,更稳定

三、二分答案:巧克力切割(蓝桥杯 P99)

3.1 题目描述

N 块巧克力,第 i 块是 H_i × W_i 的长方形。要切出 K 块大小相同的正方形,求最大可能的边长。

输入

复制代码
N K
H1 W1
H2 W2
...
HN WN

输出:最大边长

3.2 思路分析

单调性 :边长 x 越大,能切出的块数越少。具有单调性,可以二分。

check(x) :边长为 x 时,能否切出至少 K 块?

  • 每块巧克力能切:(H // x) × (W // x)
  • 总和 >= K 则合法

二分策略 :求最大值 → check(mid) 合法时,尝试更大的:left = mid + 1

3.3 完整代码

python 复制代码
N, K = map(int, input().split())
chocolates = []
for _ in range(N):
    h, w = map(int, input().split())
    chocolates.append((h, w))

def check(x):
    """边长为x时,能否切出至少K块"""
    if x == 0:
        return True
    cnt = 0
    for h, w in chocolates:
        cnt += (h // x) * (w // x)
    return cnt >= K

# 二分答案:最大边长范围 [1, 100000]
left, right = 1, 100000
ans = 1

while left <= right:
    mid = (left + right) // 2
    if check(mid):
        ans = mid      # mid合法,记录答案
        left = mid + 1 # 尝试更大的边长
    else:
        right = mid - 1 # mid太大,缩小范围

print(ans)

3.4 推演验证

复制代码
假设:N=2, K=5, 巧克力=[6x5, 5x5]

check(3): (6//3)*(5//3)=2*1=2, (5//3)*(5//3)=1*1=1, 总计3 < 5 → 不合法
check(2): (6//2)*(5//2)=3*2=6, (5//2)*(5//2)=2*2=4, 总计10 >= 5 → 合法
check(3)不合法,check(2)合法 → 答案为2

3.5 复杂度分析

指标 复杂度 说明
时间 O(N × log(max(H,W))) 二分约 log(10^5) ≈ 17 次,每次遍历N块
空间 O(N) 存储巧克力尺寸

四、二分答案:跳石头(蓝桥杯 P364)

4.1 题目描述

河道长 L,起点到终点之间有 N 块石头(不含起点终点)。至多移走 M 块石头,使得选手比赛中的最短跳跃距离尽可能大。求最短跳跃距离的最大值。

输入

复制代码
L N M
D1
D2
...
DN

D_i 表示第 i 块石头与起点的距离。

4.2 思路分析

"最大值最小化" / "最小值最大化" → 经典二分答案题型。

单调性 :假设最短跳跃距离为 x,如果 x 可行,则所有 <= x 的距离也可行?不对,应该是:如果距离 x 可行(能通过移走不超过M块石头实现),那么所有 <= x 的距离也一定可行 。所以具有单调性,二分找最大的可行 x

check(x) :判断最短跳跃距离为 x 时,需要移走多少块石头?

  • 贪心策略:从左到右遍历,如果当前石头到上一块保留石头的距离 < x,则移走当前石头
  • 最后检查终点距离是否 >= x

4.3 完整代码

python 复制代码
L, N, M = map(int, input().split())
stones = [int(input()) for _ in range(N)]

def check(x):
    """
    判断:当最短跳跃距离为x时,移走的石头数是否不超过M
    """
    now_idx = 0  # 当前所在位置(起点为0)
    cnt = 0      # 移走的石头数
    
    for i in range(N):
        if stones[i] - now_idx < x:
            # 距离太短,必须移走这块石头
            cnt += 1
        else:
            # 距离足够,可以跳到这里
            now_idx = stones[i]
    
    # 最后检查:最后一块保留的石头到终点的距离
    if L - now_idx < x:
        if cnt < M:  # 还可以移走最后一块石头
            return True
        return False
    
    return cnt <= M

# 二分:最短跳跃距离范围 [1, L]
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)

4.4 关键细节

易错点 正确做法
忘记检查终点距离 循环结束后必须判断 L - now_idx < x
移走最后一块石头的边界 cnt < M 而非 cnt <= M,因为还要移走最后一块
贪心策略 能不移就不移,距离不够才移

五、二分答案:第K大元素(蓝桥杯 P3404)

5.1 题目描述

n × m 的矩形乘法表,第 i 行第 j 列的元素为 i × j。求第 k 大的元素。

输入n m k
输出 :第 k 大的元素

5.2 思路分析

"第K小问题" → 二分答案经典应用。

单调性 :对于数字 x,乘法表中小于等于 x 的元素个数是单调递增的。

check(x) :统计乘法表中有多少个元素 <= x

  • i 行:元素为 i×1, i×2, ..., i×m
  • i × j <= xj <= x // i
  • i 行贡献:min(m, x // i)

二分策略 :找最小的 x,使得 <= x 的元素个数 >= k

5.3 完整代码

python 复制代码
n, m, k = map(int, input().split())

def check(x):
    """统计乘法表中有多少个元素 <= x"""
    cnt = 0
    for i in range(1, n + 1):
        # 第i行:i*j <= x => j <= x // i
        cnt += min(m, x // i)
    return cnt

# 二分:答案范围 [1, n*m]
left, right = 1, n * m
ans = 0

while left <= right:
    mid = (left + right) // 2
    if check(mid) >= k:
        # mid可能偏大或正好,尝试更小的
        ans = mid
        right = mid - 1
    else:
        # mid太小,需要更大的数
        left = mid + 1

print(ans)

5.4 推演验证

复制代码
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 ✓

5.5 复杂度分析

指标 复杂度 说明
时间 O(n × log(n×m)) n,m <= 5×10^5log(2.5×10^11) ≈ 38
空间 O(1) 无需存储矩阵,直接计算

六、二分答案万能模板

python 复制代码
def binary_search_answer():
    # 1. 确定初始范围
    left, right = 最小可能值, 最大可能值
    ans = 初始值
    
    # 2. 二分循环
    while left <= right:
        mid = (left + right) // 2
        
        if check(mid):  # mid满足条件
            ans = mid   # 记录答案
            # 根据题目要求调整:
            # 求最大值 → left = mid + 1
            # 求最小值 → right = mid - 1
            left = mid + 1   # 或 right = mid - 1
        else:
            # 不满足条件,反向调整
            right = mid - 1  # 或 left = mid + 1
    
    return ans

def check(x):
    """
    判断x是否满足条件
    根据题目具体实现
    """
    pass

七、题型识别指南

关键词 算法类型 例题
"有序数组中找元素" 二分查找 基础模板
"保留n位小数" 浮点二分 √2计算
"最大值最小化" / "最小值最大化" 二分答案 跳石头
"第K大/小" 二分答案 乘法表
"能否完成" + 求最优参数 二分答案 巧克力切割
"至少/至多" + 求边界 二分答案 多数二分题

八、常见错误与调试技巧

错误 现象 解决方法
死循环 left=3, right=4, mid=3,循环不变 检查更新逻辑,left = mid + 1 而非 left = mid
整数溢出 left + right 超出范围 使用 mid = left + (right - left) // 2
边界错误 答案差1 仔细分析 check() 的等号情况
浮点精度不足 输出与预期差0.001 减小 eps,或增加循环次数
二分方向搞反 该往左缩却往右缩 画数轴,明确"合法"区间方向

九、学习心得

二分的本质是"利用单调性进行决策排除" 。每次判断 mid 后,就能确定答案在左半还是右半区间,从而将问题规模减半。

三句话记住二分答案

  1. 判单调:答案是否具有单调性?
  2. 写check :给定 mid,能否快速判断是否合法?
  3. 调方向:求最大还是最小?合法时往哪边缩?

掌握二分答案后,你会发现很多看似复杂的优化问题,都能转化为"猜答案 + 验证"的套路,大幅降低思维难度。

相关推荐
dfdfadffa1 小时前
mysql如何排查网络延迟引起的数据库连接问题_使用ping测试
jvm·数据库·python
昵称小白1 小时前
子串专题部分
数据结构·算法·哈希算法
2303_821287381 小时前
JavaScript中Redux-Thunk处理异步Action的任务流
jvm·数据库·python
bzmK1DTbd1 小时前
MongoDB聚合框架:Java驱动下的数据聚合操作
java·python·mongodb
H_BB1 小时前
第17届蓝桥杯备战历程
c++·算法·职场和发展·蓝桥杯
2301_782040451 小时前
JavaScript中类 Class 语法的可读性与维护性优势
jvm·数据库·python
2401_871492851 小时前
HTML函数在旧版Windows跑得动吗_系统版本与硬件协同影响【指南】
jvm·数据库·python
kexnjdcncnxjs2 小时前
如何利用宝塔面板进行数据迁移_使用宝塔整机备份功能
jvm·数据库·python
anew___2 小时前
算法分析与设计课程全算法核心概述|期末复习+知识梳理
算法