# Вставка с использованием двоичного поиска Двоичный поиск можно использовать не только для поиска целевого элемента, но и для решения множества других задач, таких как поиск позиции для вставки целевого элемента. ## Случай без повторяющихся элементов !!! question Дан упорядоченный массив `nums` длиной $n$ и элемент `target`, массив не содержит повторяющихся элементов. Необходимо вставить `target` в массив `nums`, сохранив его упорядоченность. Если массив уже содержит элемент `target`, вставить его слева от существующего. Вернуть индекс `target` в массиве после вставки. Пример показан на рисунке ниже. ![Пример данных для вставки с использованием двоичного поиска](../assets/binary_search_insertion_example.png) Если требуется повторно использовать код двоичного поиска из предыдущего раздела, необходимо ответить на следующие два вопроса: **Вопрос первый**: если массив содержит `target`, является ли индекс вставки индексом этого элемента? Условие задачи требует вставить `target` слева от равного элемента, т. е. новый `target` заменяет старое положение `target`. То есть **если массив уже содержит `target`, индекс вставки совпадает с индексом этого `target`**. **Вопрос второй**: если массив не содержит `target`, какой элемент будет иметь индекс вставки? Дальнейший процесс двоичного поиска: когда `nums[m] < target`, указатель $i$ перемещается, т. е. приближается к элементу, большему или равному `target`. Аналогично указатель $j$ всегда приближается к элементу, меньшему или равному `target`. Таким образом, по окончании двоичного поиска указатель $i$ указывает на первый элемент, больший `target`, а указатель $j$ -- на первый элемент, меньший `target`. **Легко понять, что, если массив не содержит `target`, индекс вставки будет равен $i$**. Ниже приведен пример кода. ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple} ``` ## Случай с повторяющимися элементами !!! question На основе предыдущей задачи, предположим, что массив может содержать повторяющиеся элементы, остальные условия остаются неизменными. Если в массиве существует несколько одинаковых `target`, то обычный двоичный поиск может вернуть индекс только одного из них, **не определяя, сколько `target` находится слева и справа от этого элемента**. Задача требует вставить целевой элемент в самое левое положение, **поэтому необходимо найти индекс самого левого `target` в массиве**. Первоначально предполагается реализовать решение следующим образом (см. рисунок ниже): 1. Выполнить двоичный поиск и получить индекс любого `target`, обозначить его как $k$. 2. Начиная с индекса $k$, выполнить линейный обход влево и вернуть индекс, когда будет найден самый левый `target`. ![Линейный поиск точки вставки для повторяющихся элементов](../assets/binary_search_insertion_naive.png) Это рабочий метод, но он включает линейный поиск, поэтому его временная сложность составляет $O(n)$. Когда в массиве много повторяющихся `target`, эффективность этого метода низка. Теперь рассмотрим расширение алгоритма двоичного поиска. Общий процесс остается неизменным: на каждом этапе сначала вычисляется средний индекс $m$, затем определяется отношение между `target` и `nums[m]`. Возможны следующие случаи: - Когда `nums[m] < target` или `nums[m] > target`, тогда `target` еще не найден, поэтому используется операция сужения интервала обычного двоичного поиска, **чтобы указатели $i$ и $j$ приближались к `target`**. - Когда `nums[m] == target`, тогда элементы, меньшие `target`, находятся в интервале $[i, m - 1]$. Поэтому используется операция $j = m - 1$ для сужения интервала, **чтобы указатель $j$ приблизился к элементам, меньшим `target`**. После завершения цикла $i$ будет указывать на самый левый `target`, а $j$ -- на первый элемент, меньший `target`. **Поэтому индекс $i$ является точкой вставки**. === "<1>" ![Этапы двоичного поиска точки вставки для повторяющихся элементов](../assets/binary_search_insertion_step1.png) === "<2>" ![binary_search_insertion_step2](../assets/binary_search_insertion_step2.png) === "<3>" ![binary_search_insertion_step3](../assets/binary_search_insertion_step3.png) === "<4>" ![binary_search_insertion_step4](../assets/binary_search_insertion_step4.png) === "<5>" ![binary_search_insertion_step5](../assets/binary_search_insertion_step5.png) === "<6>" ![binary_search_insertion_step6](../assets/binary_search_insertion_step6.png) === "<7>" ![binary_search_insertion_step7](../assets/binary_search_insertion_step7.png) === "<8>" ![binary_search_insertion_step8](../assets/binary_search_insertion_step8.png) Рассмотрим следующий код: ветви условий `nums[m] > target` и `nums[m] == target` выполняют одинаковые операции, поэтому их можно объединить. Тем не менее мы можем оставить условия развернутыми, поскольку логика становится более ясной и читаемой. ```src [file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion} ``` !!! tip Код в этом разделе использует запись «двойной замкнутый интервал». Заинтересованные читатели могут самостоятельно реализовать вариант «левозамкнутый правооткрытый интервал». В целом двоичный поиск заключается в установке целей поиска для указателей $i$ и $j$. Целью может быть конкретный элемент (например, `target`) или диапазон элементов (например, элементы, меньшие `target`). В процессе непрерывного циклического деления указатели $i$ и $j$ постепенно приближаются к заранее установленной цели. В конечном итоге они либо успешно находят ответ, либо останавливаются после пересечения границ.