Skip to content

二分答案与三分法

基于通用算法套路 · 核于 2026-07

速查

  • 二分答案核心思想:不再对「下标」二分,而是对「答案值域」[L, R] 二分——每次取候选答案 mid,调用 check(mid) 判定是否可行,靠「可行性关于答案单调」折半值域,最终收敛到最优答案。
  • 适用条件(三要素):①答案有明确上下界(值域有限或可限定);②check(x) 可在多项式时间完成;③可行性单调——「答案越大越难满足」或「越小越难」(即存在一个临界值,一侧全可行、另一侧全不可行)。
  • 模板形态:求「最大化最小值」类,找最大的 x 使 check(x) 为真——check(mid) ? lo = mid : hi = mid - 1(上取整 mid);求「最小化最大值」类,找最小的 x 使 check(x) 为真——check(mid) ? hi = mid : lo = mid + 1(下取整 mid)。
  • check 的设计是核心check(x) 通常是个贪心或模拟,判断「在答案不超过/不低于 x 的约束下能否达成目标」。
  • 复杂度:O(log(值域) × 复杂度(check))。值域越大迭代越多,但 log 增长慢,常可接受。
  • 经典题型:「最大化最小值」「最小化最大值」「第 k 小」「可行性判定」——如分裂数组最大值最小化、木头切割得至少 k 段、吃香蕉速度、运送货物天数。
  • 三分法(Ternary Search):当目标函数 f(x) **单峰(先增后减)或单谷(先减后增)**时,取两个内分点 m1 = lo + (hi-lo)/3m2 = hi - (hi-lo)/3,比较 f(m1)f(m2),每次排除左或右三分之一的区间,逼近极值。
  • 三分判定:单峰时 f(m1) < f(m2) ⇒ 极值在 m1 右侧(lo = m1),否则在 m2 左侧(hi = m2);单谷时方向相反。
  • 三分复杂度:每次区间缩到 2/3,O(log n)(底数 1.5);浮点场景需设精度 eps 或固定迭代次数(如 100 次保证精度)。
  • 三分 vs 二分答案:三分要函数凸/凹(单峰/单谷),二分答案要判定单调(一侧可行一侧不可行);多峰函数三分不适用。
  • 易错点:二分答案的 mid 取整方向(避免死循环)、check 的单调性验证、三分浮点的精度与终止条件。

一、二分答案:对值域二分 + check 验证

朴素二分搜索的是「下标」,二分答案搜索的是「答案」。典型场景:「求满足某条件的最优解」,且可行性关于答案单调

思想

把「找最优答案」转化为「反复判定一个候选答案可不可行」:

答案轴:  L ─────────────── R
               ↑ mid(候选答案)
          check(mid)?
       真 → 答案可以更大/更小(往一侧缩)
       假 → 答案不可行(往另一侧缩)

只要「可行 / 不可行」在答案轴上是连续的单调区间(一侧全可行、一侧全不可行),就能折半。

适用条件(务必先验证)

  1. 答案有界:能确定答案的最小值 L 和最大值 R(如「分裂数组最大值」在 [max(nums), sum(nums)] 之间)。
  2. check 多项式可解:给定答案 x,能在 O(n) 或 O(n log n) 内判定是否可行。
  3. 可行性单调:这是最关键的前提——若 x 可行则所有「更宽松」的答案也可行(或反之)。例如「最大值不超过 x」中,x 越大约束越宽松越易满足。

模板:求最大的 x 使 check(x) 为真(「最大化最小值」类)

js
// 典型:把数组分成 k 段,使各段和的最大值最小化 → 反过来是「最大的 x 使 check 真」
function binaryAnswerMax(nums, k) {
  let lo = Math.max(...nums), hi = nums.reduce((a, b) => a + b); // 答案上下界
  while (lo < hi) {
    const mid = (lo + hi + 1) >> 1;         // 上取整,避免死循环
    check(nums, mid, k) ? (lo = mid) : (hi = mid - 1);
  }
  return lo;
}
// check:限制每段和 ≤ limit 时,至少要分多少段;≤ k 段则可行

经典题:木头切割(至少得 k 段)

有 n 根木头,每根长 L[i],要切成等长 x 的小段(不计余料),至少得到 k 段。求 x 的最大值。

check(x):每根能切 floor(L[i]/x) 段,总和 ≥ k 则可行。x 越大切的段越少——可行性单调(小 x 易可行,大 x 难)。对 x 二分即可。

js
function maxWoodLength(L, k) {
  let lo = 1, hi = Math.max(...L);
  while (lo < hi) {
    const mid = (lo + hi + 1) >> 1;          // 上取整
    const segs = L.reduce((s, len) => s + Math.floor(len / mid), 0);
    segs >= k ? (lo = mid) : (hi = mid - 1);
  }
  return lo;
}

经典题:分裂数组最大值最小化

把数组分成 m 个连续子数组,使各子数组和的最大值最小。

答案在 [max(nums), sum(nums)]check(limit):贪心模拟,每段加到不超过 limit 就新开一段,统计段数 ≤ m 则可行。答案越大越易满足——单调,可二分找最小的可行 limit

js
function splitArray(nums, m) {
  let lo = Math.max(...nums), hi = nums.reduce((a, b) => a + b, 0);
  while (lo < hi) {
    const mid = (lo + hi) >> 1;              // 下取整
    let cnt = 1, sum = 0;
    for (const x of nums) {
      if (sum + x > mid) { cnt++; sum = 0; }
      sum += x;
    }
    cnt <= m ? (hi = mid) : (lo = mid + 1);  // 段数少说明 limit 够大
  }
  return lo;
}

二、三分法:单峰函数求极值

当目标函数 f(x) 是**单峰(凸,先增后减)单谷(凹,先减后增)**时,用三分法在连续定义域上找极值。

思想

取两个内分点 m1 < m2,把区间三等分,比较 f(m1)f(m2)

单峰 f:       /\
            m1  m2   (极值在中间)
   f(m1) < f(m2) → 极值在 m1 右侧 → lo = m1
   f(m1) > f(m2) → 极值在 m2 左侧 → hi = m2

每次区间缩到 2/3,几何级数收敛,O(log n)(底数 1.5)。

模板(浮点,单峰求最大值)

js
function ternarySearch(f, lo, hi) {
  const eps = 1e-8;                          // 精度
  while (hi - lo > eps) {
    const m1 = lo + (hi - lo) / 3;
    const m2 = hi - (hi - lo) / 3;
    f(m1) < f(m2) ? (lo = m1) : (hi = m2);   // 单峰:小的一侧排除
  }
  return (lo + hi) / 2;                       // 近似极值点
}

整数三分(求离散单峰极值下标):用循环 while (hi - lo > 2),比较 f(m1)f(m2) 缩区间,最后在剩余 2-3 个点里取极值。

适用前提与陷阱

  • 必须严格单峰/单谷:多峰函数三分会收敛到某个局部极值,可能不是全局最优。
  • 浮点精度eps 取题目要求的精度再小 1-2 位;或固定迭代 100-200 次(每次精度约提升 1.5 倍,100 次足够)。
  • 离散三分注意终止:区间小于 3 时停止枚举,避免 m1 == m2 死循环。

三、二分答案 vs 二分下标 vs 三分

维度朴素二分(下标)二分答案(值域)三分(自变量)
搜索对象数组下标答案的值函数自变量
单调性来源数组有序可行性关于答案单调函数单峰/单谷(凸性)
判定nums[mid]targetcheck(mid)f(m1)f(m2)
目标找定值找最优可行答案找极值
典型题有序数组查值最大化最小值、第 k 小凸函数求最值

四、易错点

  • mid 取整方向:「找最大可行答案」用上取整 (lo+hi+1)>>1lo = mid;「找最小可行答案」用下取整 (lo+hi)>>1hi = mid。配错会死循环。
  • check 单调性必须验证:若可行性不单调(如某些双峰分布),二分答案失效。
  • 三分只适用单峰:多峰要先证明单峰性(如距离函数常是凸的)。
  • 浮点三分迭代次数:宁可多迭代(100 次)也别太少导致精度不够。

交互演示

下一步

掌握了三变体后,汇总速查模板、复杂度表与适用场景对比,见参考