乐于分享
好东西不私藏

一种二分算法的理解和模板

一种二分算法的理解和模板
吃透二分查找/二分答案:            正向&反向双模板,告别死循环!
二分查找是算法入门的核心知识点,但很多人练了无数题,依然会在「区间收缩」「mid 取值」「循环条件」上踩坑——要么死循环,要么找错答案。今天我想分享一套自己总结的二分分类法:把二分查找分为正向查找和反向查找,从逻辑根源拆解,让大家告别模板记混、边界出错的问题。

一、正向查找:找满足条件的「最小值」

先从最易理解的正向查找入手,这是二分答案的基础款,也是我们推导逻辑的起点。

1. 核心特征

二分答案类题目,正向查找有明确的结构:
  • 答案需要满足某个判定条件;
  • 数值越大,越容易满足该条件;
  • 题目要求:找到满足条件的最小数值。
通俗举例:找最小的能装下所有物品的箱子尺寸——箱子越大,越容易装下物品,我们要找“刚好能装下”的最小尺寸。

2. 区间收缩逻辑

确定查找区间 [l, r](闭区间),核心是通过检查 mid 是否满足条件缩窄区间,一步步逼近答案。 取 mid = (l + r) / 2(默认下取整),分两种情况: 1. mid 不满足条件 mid 及其左侧所有值都不满足(数值越小越难满足),直接排除,令 l = mid + 1。 2. mid 满足条件 mid 右侧所有值也满足,但我们要找最小满足值,右侧全部排除(保留 mid),令 r = mid。

关键提醒:为什么不能让 r = mid - 1? 因为 mid 本身可能就是最终答案,直接减 1 会把正确答案排除出区间。

3. 循环条件推导

循环终止的核心:聚焦区间只剩 2 个值(r = l + 1)的临界场景。
  • 答案是 l:mid = l,满足条件 → r = mid = l,区间缩为 [l, l]。
  • 答案是 r:mid = l,不满足条件 → l = mid + 1 = r,区间缩为 [r, r]。
两种场景最终都收敛到 l = r,因此:
  • 循环执行条件:l < r
  • 循环终止条件:l == r(此时 l/r 就是答案)

4. 正向查找代码模板

// 正向查找:数值越大越易满足条件,找满足条件的最小值boolcheck(int mid) {    // 自定义:判断 mid 是否满足题目条件}intfindMinValid() {    int l = 初始左边界, r = 初始右边界;    while (l < r) {        int mid = (l + r) / 2// 下取整        if (!check(mid)) l = mid + 1;        else r = mid;    }    // 验证是否有满足条件的值    return check(l) ? l : -1;}

二、反向查找:找满足条件的「最大值」

反向查找是二分的易错重灾区,看似只是方向反了,核心问题藏在 mid 的取值方式 里。

1. 核心特征

反向查找与正向逻辑完全相反:
  • 答案需要满足某个判定条件;
  • 数值越小,越容易满足该条件;
  • 题目要求:找到满足条件的最大数值。
通俗举例:找最大的能通过安检的行李重量——重量越小,越容易通过安检,我们要找“刚好能通过”的最大重量。

2. 初始逻辑踩坑

沿用正向的 mid = (l + r) / 2,区间收缩规则:
  • mid 不满足 → 排除右侧,r = mid - 1
  • mid 满足 → 排除左侧,l = mid
但临界场景(区间只剩 2 个值)会出现死循环/答案错乱:
  • 答案是 l:mid = l,满足 → l 不变,死循环
  • 答案是 r:mid = l,不满足 → r = l-1,区间错乱

3. 问题本质与修正

根源:mid = (l + r) / 2 是下取整,天然偏向左侧,打破反向查找的平衡。 解决方案:反向查找改用 mid 上取整,写法:

intmid=(l+r+1)/2;

4. 修正后临界验证(区间只剩 2 个值:r = l + 1)

  • 答案是 l:mid = r,不满足 → r = mid - 1 = l,区间缩为 [l, l]
  • 答案是 r:mid = r,满足 → l = mid = r,区间缩为 [r, r]
完美收敛,无死循环、无答案错乱。

5. 反向查找代码模板

// 反向查找:数值越小越易满足条件,找满足条件的最大值boolcheck(int mid) {    // 自定义:判断 mid 是否满足题目条件}intfindMaxValid() {    int l = 初始左边界, r = 初始右边界;    while (l < r) {        int mid = (l + r + 1) / 2// 上取整,关键修正        if (!check(mid)) r = mid - 1;        else l = mid;    }    // 验证是否有满足条件的值    return check(l) ? l : -1;}

三、核心总结:二分查找的「对称美」

正向与反向查找,逻辑完全对称,仅 3 个关键点不同: 1. 目标:正向找最小满足值,反向找最大满足值 2. mid 取值:正向(l+r)/2(下取整),反向(l+r+1)/2(上取整) 3. 区间收缩:正向不满足左移,反向不满足右移

记忆口诀

  • 找最小:mid 下取整,不满足 l=mid+1,满足 r=mid
  • 找最大:mid 上取整,不满足 r=mid-1,满足 l=mid
  • 循环统一:l < r,最终 l=r 即答案,记得验证

避坑关键

二分的坑不在逻辑,而在边界。理解 mid 取值的偏向性、临界区间的收敛性,不用死记模板,也能写出正确代码。

四、完整模板

#include<bits/stdc++.h>using namespace std;// 自定义判定函数(根据题目修改)boolcheck(int mid){    return true;}// 正向查找:找满足条件的最小值intbinarySearchForward(int l, int r){    while (l < r) {        int mid = (l + r) / 2;        if (!check(mid)) l = mid + 1;        else r = mid;    }    return check(l) ? l : -1;}// 反向查找:找满足条件的最大值intbinarySearchBackward(int l, int r){    while (l < r) {        int mid = (l + r + 1) / 2;        if (!check(mid)) r = mid - 1;        else l = mid;    }    return check(l) ? l : -1;}intmain(){    int l = 0, r = 100;    int minValid = binarySearchForward(l, r);    int maxValid = binarySearchBackward(l, r);    cout << "满足条件的最小值:" << minValid << endl;    cout << "满足条件的最大值:" << maxValid << endl;    return 0;}
希望这篇拆解能帮你彻底吃透二分查找——与其刷 100 道题记 100 个模板,不如搞懂 1 个逻辑,推导所有情况。二分的本质是区间的收敛,理解这一点,无论题目怎么变形,都能游刃有余~