Признак в условии
- вход отсортирован
- «минимальное значение, при котором …» - свойство монотонно
- требуется сложность лучше линейной
Два шаблона - выучить оба
Точное совпадение, включительные границы:
let lo = 0, hi = n - 1;
while (lo <= hi) {
const mid = lo + ((hi - lo) >> 1);
if (a[mid] === target) return mid;
if (a[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return -1;
Левая граница, полуинтервал - им решается всё остальное, включая поиск по ответу:
let lo = 0, hi = n; // hi за последним элементом
while (lo < hi) {
const mid = lo + ((hi - lo) >> 1);
if (!ok(mid)) lo = mid + 1; // mid точно не ответ
else hi = mid; // mid может быть ответом: не отбрасываем
}
return lo;
Поиск по ответу
Когда ответ - число из диапазона, а проверка «подходит ли» считается за O(n): скорость поедания бананов, минимальная вместимость корабля, максимальная длина отрезка. Диапазон [1, max], функция ok(x) монотонна - дальше второй шаблон.
Сложность
Время O(log n) или O(n log range) при поиске по ответу.
Частые ошибки
- смешивают шаблоны:
while (lo < hi)сhi = mid - 1теряет ответ hi = midприwhile (lo <= hi)даёт бесконечный цикл(lo + hi) / 2может переполниться в языках с 32-битными целыми (в JS не страшно, но спрашивают)- на повёрнутом массиве строгое сравнение вместо нестрогого ломается на длине 2
Мнемоника
«Инвариант, середина, выбрасываем половину»
Не путать с
- Два указателя - бинарный поиск отбрасывает половину; указатели идут по всем элементам. Если ответ нельзя проверить за шаг, поиска по ответу нет
- Жадность - в поиске по ответу проверка «подходит ли X» часто сама жадная - это не другой паттерн, а его часть
Задачи на этот паттерн
- Бинарный поиск
- Левая граница
- Поиск в повёрнутом массиве
- Минимум в повёрнутом массиве
- Бинарный поиск по ответу
- Поиск в отсортированной матрице
- Позиция вставки
- Любой локальный максимум
- Целочисленный корень