Классическая формулировка: дан отсортированный массив `arr` и целевое значение `target`. Необходимо найти индекс элемента, равного `target`, или вернуть -1, если такого элемента нет. Каноничная ошибкоопасная реализация выглядит так: `left = 0; right = arr.length - 1; while (left 3`, поэтому `right=2`. Теперь `left=0, right=2`. `mid=1`, `arr[1]=3` — успех! В этом случае ошибка не проявилась. Но возьмем `target=1`. Первый шаг тот же: `left=0, right=5, mid=2, arr[2]=5 > 1 => right=2`. Второй шаг: `left=0, right=2, mid=1, arr[1]=3 > 1 => right=1`. Третий шаг: `left=0, right=1, mid=0, arr[0]=1` — успех. Снова повезло. Теперь `target=9`. Первый шаг: `left=0, right=5, mid=2, arr[2]=5 < 9 => left=2`. Второй шаг: `left=2, right=5, mid=3, arr[3]=7 < 9 => left=3`. Третий шаг: `left=3, right=5, mid=4, arr[4]=9` — успех. Кажется, работает? Но вот `target=2` (отсутствует). Шаг1: `left=0, right=5, mid=2, arr[2]=5 > 2 => right=2`. Шаг2: `left=0, right=2, mid=1, arr[1]=3 > 2 => right=1`. Шаг3: `left=0, right=1, mid=0, arr[0]=1 < 2 => left=0`. Шаг4: `left=0, right=1` — условие `while (left < right)` все еще истинно, мы попадаем в бесконечный цикл, так как `mid` снова будет 0, `left` останется 0. Ошибка нашлась! Отладка на отсутствующем элементе — ключевой прием.
Исправляем строку: `if (arr[mid] < target) { left = mid + 1; }`. Теперь для `target=2`: после шага3 `left` станет 1, условие цикла `while (1 < 1)` ложно, функция вернет -1. Успех.
Еще один уровень сложности — поиск не точного значения, а границы (первое вхождение, последнее вхождение, позиция для вставки). Например, найти индекс первого элемента, большего или равного `target` (lower_bound). Здесь цикл часто останавливается при `left == right`, а ответом является `left`. Отладка таких вариаций требует тестирования на краевых случаях: пустой массив, массив из одного элемента, `target` меньше всех, больше всех, равен первому/последнему элементу, находится в середине.
Практическая методика отладки:
- **Ручное трассирование** на небольших массивах (3-5 элементов) для всех возможных `target` (включая отсутствующие). Записывайте значения `left`, `right`, `mid` на каждом шаге.
- **Использование инварианта**. Сформулируйте условие, которое должно сохраняться на каждой итерации. Для поиска точного значения: "Если `target` присутствует в исходном массиве, то его индекс находится в интервале `[left, right]`". Проверяйте, не нарушается ли инвариант после обновления границ.
- **Тестирование краевых случаев** автоматическими unit-тестами. Массивы: `[]`, `[1]`, `[1,2]`, `[1,3]` (с пропуском). Цели: первый элемент, последний, средний, отсутствующий (меньше минимума, больше максимума, между элементами).
- **Визуализация**. Нарисуйте массив и отметьте текущие `left`, `mid`, `right`. Это помогает понять логику обновления границ.
- **Сравнение с эталоном**. Если сомневаетесь, сравните результат работы вашего алгоритма с линейным поиском на большом наборе случайных данных.
Комментарии (13)