mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-06 04:34:20 +00:00
56 lines
4.9 KiB
Markdown
56 lines
4.9 KiB
Markdown
# Поиск границ с использованием двоичного поиска
|
|
|
|
## Поиск левой границы
|
|
|
|
!!! question
|
|
|
|
Дан упорядоченный массив `nums` длиной $n$, который может содержать повторяющиеся элементы. Необходимо вернуть индекс самого левого элемента `target` в массиве. Если массив не содержит этот элемент, вернуть $-1$.
|
|
|
|
Вспомним метод поиска точки вставки с использованием двоичного поиска: после завершения поиска $i$ указывает на самый левый `target`, **поэтому поиск точки вставки по сути является поиском индекса самого левого** `target`.
|
|
|
|
Рассмотрим реализацию поиска левой границы через функцию поиска точки вставки. Обратите внимание, что массив может не содержать `target`, что может привести к следующим двум результатам:
|
|
|
|
- Индекс точки вставки $i$ выходит за границы массива.
|
|
- Элемент `nums[i]` не равен `target`.
|
|
|
|
При возникновении любой из этих двух ситуаций можно просто вернуть $-1$. Код приведен ниже:
|
|
|
|
```src
|
|
[file]{binary_search_edge}-[class]{}-[func]{binary_search_left_edge}
|
|
```
|
|
|
|
## Поиск правой границы
|
|
|
|
Как же найти самый правый `target`? Самый прямой способ -- изменить код, заменив операцию сужения указателей в случае `nums[m] == target`. Код здесь опущен, заинтересованные читатели могут реализовать его самостоятельно.
|
|
|
|
Ниже мы рассмотрим два более изящных метода.
|
|
|
|
### Повторное использование поиска левой границы
|
|
|
|
На самом деле мы можем использовать функцию поиска самого левого элемента для поиска самого правого элемента, конкретный метод: **преобразовать поиск самого правого** `target` **в поиск самого левого** `target + 1`.
|
|
|
|
Как показано на рисунке ниже, после завершения поиска указатель $i$ указывает на самый левый `target + 1` (если он существует), а $j$ указывает на самый правый `target`, **поэтому достаточно вернуть** $j$.
|
|
|
|

|
|
|
|
Обратите внимание, что возвращаемая точка вставки -- это $i$, поэтому необходимо вычесть из нее $1$, чтобы получить $j$:
|
|
|
|
```src
|
|
[file]{binary_search_edge}-[class]{}-[func]{binary_search_right_edge}
|
|
```
|
|
|
|
### Преобразование в поиск элемента
|
|
|
|
Мы знаем, что когда массив не содержит `target`, в конечном итоге $i$ и $j$ будут указывать соответственно на первый элемент, больший и меньший `target`.
|
|
|
|
Поэтому, как показано на рисунке ниже, мы можем создать элемент, которого не существует в массиве, для поиска левой и правой границ.
|
|
|
|
- Поиск самого левого `target`: можно преобразовать в поиск `target - 0.5` и вернуть указатель $i$.
|
|
- Поиск самого правого `target`: можно преобразовать в поиск `target + 0.5` и вернуть указатель $j$.
|
|
|
|

|
|
|
|
Код здесь опущен, следует отметить следующие два момента:
|
|
|
|
- Данный массив не содержит дробных чисел, что означает, что нам не нужно беспокоиться о том, как обрабатывать случай равенства.
|
|
- Поскольку этот метод вводит дробные числа, необходимо изменить тип переменной `target` в функции на тип с плавающей точкой (в Python изменения не требуются). |