Files
hello-algo/ru/docs/chapter_searching/binary_search_insertion.md
2026-01-20 15:08:42 +08:00

8.7 KiB
Raw Blame History

Вставка с использованием двоичного поиска

Двоичный поиск можно использовать не только для поиска целевого элемента, но и для решения множества других задач, таких как поиск позиции для вставки целевого элемента.

Случай без повторяющихся элементов

!!! question

Дан упорядоченный массив `nums` длиной $n$ и элемент `target`, массив не содержит повторяющихся элементов. Необходимо вставить `target` в массив `nums`, сохранив его упорядоченность. Если массив уже содержит элемент `target`, вставить его слева от существующего. Вернуть индекс `target` в массиве после вставки. Пример показан на рисунке ниже.

Пример данных для вставки с использованием двоичного поиска

Если требуется повторно использовать код двоичного поиска из предыдущего раздела, необходимо ответить на следующие два вопроса:

Вопрос первый: если массив содержит target, является ли индекс вставки индексом этого элемента?

Условие задачи требует вставить target слева от равного элемента, т. е. новый target заменяет старое положение target. То есть если массив уже содержит target, индекс вставки совпадает с индексом этого target.

Вопрос второй: если массив не содержит target, какой элемент будет иметь индекс вставки?

Дальнейший процесс двоичного поиска: когда nums[m] < target, указатель i перемещается, т. е. приближается к элементу, большему или равному target. Аналогично указатель j всегда приближается к элементу, меньшему или равному target.

Таким образом, по окончании двоичного поиска указатель i указывает на первый элемент, больший target, а указатель j -- на первый элемент, меньший target. Легко понять, что, если массив не содержит target, индекс вставки будет равен $i$. Ниже приведен пример кода.

[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion_simple}

Случай с повторяющимися элементами

!!! question

На основе предыдущей задачи, предположим, что массив может содержать повторяющиеся элементы, остальные условия остаются неизменными.

Если в массиве существует несколько одинаковых target, то обычный двоичный поиск может вернуть индекс только одного из них, не определяя, сколько target находится слева и справа от этого элемента.

Задача требует вставить целевой элемент в самое левое положение, поэтому необходимо найти индекс самого левого target в массиве. Первоначально предполагается реализовать решение следующим образом (см. рисунок ниже):

  1. Выполнить двоичный поиск и получить индекс любого target, обозначить его как k.
  2. Начиная с индекса k, выполнить линейный обход влево и вернуть индекс, когда будет найден самый левый target.

Линейный поиск точки вставки для повторяющихся элементов

Это рабочий метод, но он включает линейный поиск, поэтому его временная сложность составляет 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>" Этапы двоичного поиска точки вставки для повторяющихся элементов

=== "<2>" binary_search_insertion_step2

=== "<3>" binary_search_insertion_step3

=== "<4>" binary_search_insertion_step4

=== "<5>" binary_search_insertion_step5

=== "<6>" binary_search_insertion_step6

=== "<7>" binary_search_insertion_step7

=== "<8>" binary_search_insertion_step8

Рассмотрим следующий код: ветви условий nums[m] > target и nums[m] == target выполняют одинаковые операции, поэтому их можно объединить.

Тем не менее мы можем оставить условия развернутыми, поскольку логика становится более ясной и читаемой.

[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion}

!!! tip

Код в этом разделе использует запись «двойной замкнутый интервал». Заинтересованные читатели могут самостоятельно реализовать вариант «левозамкнутый правооткрытый интервал».

В целом двоичный поиск заключается в установке целей поиска для указателей i и j. Целью может быть конкретный элемент (например, target) или диапазон элементов (например, элементы, меньшие target).

В процессе непрерывного циклического деления указатели i и j постепенно приближаются к заранее установленной цели. В конечном итоге они либо успешно находят ответ, либо останавливаются после пересечения границ.