三分查找(单峰极值)
查找 · 双探针对决 · 每轮丢掉 1/3
山顶在哪:二分为什么失灵
单峰数组先一路爬升、过峰后一路下降——像一座山。找峰顶时二分却失灵了:只看一个 a[mid],你根本不知道自己站在上坡还是下坡,没法决定往哪走。
两个探针,制造方向感
三分查找在区间里放两个三分点探针 m1、m2,让它们「对决」:若 a[m1] < a[m2],峰不可能在 m1 左侧(那边只会更矮)——丢掉左侧 1/3;否则丢右侧 1/3。每轮区间变 2/3,很快登顶。下面蓝/黄箭头就是两个探针(红/绿是区间端点),高亮的两根柱在对决,暗下去的是已排除区域——四轮登顶 12。右侧代码随每一步同步高亮。
单峰数组:一路爬升到峰顶再一路下降。二分在这儿失灵——只看 a[mid] 不知道自己在上坡还是下坡。三分的答案:放两个探针制造方向感
1function ternaryPeak(a: number[]): number {
2 let lo = 0, hi = a.length - 1; // 单峰数组
3 while (lo < hi) {
4 const third = ((hi - lo) / 3) | 0;
5 const m1 = lo + third, m2 = hi - third; // 两个三分探针
6 if (a[m1] < a[m2]) lo = m1 + 1; // 峰不在 m1 左侧:丢左 1/3
7 else hi = m2 - 1; // 峰不在 m2 右侧:丢右 1/3
8 }
9 return lo; // 峰顶
10}
11// 坡度二分变体:比较 a[mid] 与 a[mid+1],上坡去右、下坡去左
形状单峰(先升后降)
[lo, hi][0, 8]
m1 / m2— / —
候选数9
1 / 7
复杂度与同族变体
正确性:a[m1] < a[m2] ⟹ 峰在 (m1, hi](严格单峰下 m1 左侧必更矮)。
复杂度:每轮 ×2/3 → O(log₍₃⁄₂₎ n),与二分同阶。
坡度二分:比较
实数版:凸/凹函数极值在实数域三分,固定轮数(如 100 轮)收敛到精度 ε。
复杂度:每轮 ×2/3 → O(log₍₃⁄₂₎ n),与二分同阶。
坡度二分:比较
a[mid] 与 a[mid+1]——上坡去右、下坡去左,一个探针也能有方向感(log₂ n)。实数版:凸/凹函数极值在实数域三分,固定轮数(如 100 轮)收敛到精度 ε。
至此查找大类五页收齐:找值 → 找边界 → 断崖找值 → 找答案 → 找峰。所有套路共享一个灵魂:每一步用 O(1) 的信息,安全地扔掉一大块候选。