二分答案是一种特殊的二分查找应用,它不直接查找数据,而是通过二分思想来验证某个答案是否满足条件。我第一次接触这个概念是在解决"跳石头"问题时——需要在河中间摆放的石头中移走若干块,使得选手跳跃时的最短距离最大化。当时我尝试用暴力枚举,结果时间复杂度直接爆炸,这才意识到二分答案的精妙。
识别二分答案题目的三大黄金特征:
最值问题:题目往往要求"最大值的最小化"或"最小值的最大化"。比如"砍树"问题中,我们需要找到锯片的最大高度,使得得到的木材总长度恰好满足需求。
单调性验证:存在一个临界点,当答案大于/小于这个临界点时,条件满足情况会发生突变。就像"分组"问题中,当设定的最大身高差增大时,所需组数会减少,这种单调关系是二分的前提。
验证比求解简单:直接求解答案困难,但验证某个假设答案是否可行相对容易。例如在"卡牌"问题中,计算最多能组成多少套牌很难,但验证能否组成k套牌却很简单。
我常用的特征检查清单:
整数二分有两种基本形态,取决于我们需要寻找左边界还是右边界。在蓝桥杯比赛中,90%的题目都可以用以下模板解决:
python复制def binary_search():
left, right = 初始边界
answer = 初始值
while left <= right:
mid = (left + right) // 2
if check(mid): # 满足条件
answer = mid
right = mid - 1 # 或 left = mid + 1
else:
left = mid + 1 # 或 right = mid - 1
return answer
关键点解析:
left <= right可以避免遗漏边界情况,这是很多新手容易出错的地方。mid = left + (right - left) // 2。当处理精度问题时,比如求平方根或利率计算,就需要浮点数二分。它与整数二分的主要区别在于:
python复制def float_binary_search():
left, right = 初始边界
while right - left > 精度要求:
mid = (left + right) / 2
if check(mid):
right = mid # 或 left = mid
else:
left = mid # 或 right = mid
return left # 或 right
实战技巧:
1e-4left <= right,避免无限循环check函数是二分答案的灵魂,也是比赛中主要的考察点。根据我的经验,check函数主要有以下几种类型:
典型例题:"跳石头"、"丢瓶盖"。这类问题的共同特点是通过遍历统计满足条件的元素数量。
python复制def check(mid):
count = 1 # 初始已选元素
last_pos = arr[0]
for i in range(1, len(arr)):
if arr[i] - last_pos >= mid:
count += 1
last_pos = arr[i]
return count >= k # 或 <=k 取决于题意
优化技巧:
典型例题:"分组"、"数列分段"。这类问题往往需要结合贪心策略来验证。
python复制def check(max_sum):
current_sum = 0
segments = 1
for num in arr:
if current_sum + num > max_sum:
segments += 1
current_sum = num
else:
current_sum += num
return segments <= m
常见陷阱:
典型例题:"砍树"、"木材加工"。这类问题需要完整模拟操作过程。
python复制def check(height):
total = 0
for tree in trees:
if tree > height:
total += tree - height
if total >= required: # 提前返回优化
return True
return total >= required
性能优化:
这是最经典的二分答案例题,我曾在蓝桥杯模拟赛中三次遇到它的变种。题目要求通过在河中的岩石间移动,使得选手跳跃的最短距离最大化。
解题步骤:
python复制def check(d):
removed = 0
last = 0
for rock in rocks:
if rock - last < d:
removed += 1
else:
last = rock
return removed <= m
易错点:
这个问题要求找到锯片的最大高度,使得得到的木材总长度至少为M。我第一次做时忽略了树木高度可能重复的情况,导致check函数出错。
优化后的check函数:
python复制def check(h):
return sum(max(0, x - h) for x in trees) >= M
进阶思考:
这个问题需要计算用现有卡牌最多能组成多少套牌。关键在于理解空白牌的分配策略。
python复制def check(k):
blank_needed = 0
for a, b in zip(standard, blank):
if a < k:
blank_needed += k - a
if blank_needed > total_blank:
return False
return True
业务逻辑转化:
原题是将小朋友按身高分组,使每组极差不超过d。考虑以下变式:
python复制def check(d):
groups = 1
first = h[0]
for i in range(1, len(h)):
if h[i] - first > d or i - start + 1 > max_size:
groups += 1
first = h[i]
return groups <= k
在原问题基础上增加约束条件:
python复制def check(max_sum):
current_sum = 0
segments = 1
for num in arr:
if current_sum + num > max_sum or \
(segments > 1 and current_sum + num - prev_sum > delta):
segments += 1
prev_sum = current_sum
current_sum = num
else:
current_sum += num
return segments <= m and current_sum >= min_seg_sum
尝试为以下场景设计check函数:
我曾在一个项目中因为二分边界处理不当导致无限循环,最后通过以下方法解决:
python复制max_iter = 100
while left <= right and max_iter > 0:
max_iter -= 1
# 正常二分逻辑
这些边界条件曾让我在比赛中失分:
金融类题目对精度要求极高,我的经验是:
python复制def equal(a, b):
return abs(a - b) < 1e-6 or abs(a - b)/max(abs(a), abs(b)) < 1e-8
虽然二分是O(logn),但check函数可能是O(n)或更高:
我的比赛常用检查流程:
根据题目特点调整模板:
python复制# 寻找第一个满足条件的
while left <= right:
if check(mid):
answer = mid
right = mid - 1
else:
left = mid + 1
# 寻找最后一个满足条件的
while left <= right:
if check(mid):
answer = mid
left = mid + 1
else:
right = mid - 1
在实际问题中,二分答案常与其他算法协同:
对于单峰函数求极值,可以使用三分法:
python复制while right - left > eps:
m1 = left + (right - left)/3
m2 = right - (right - left)/3
if f(m1) < f(m2):
left = m1
else:
right = m2
提升二分答案能力的最佳方式是大量练习: