mirror of
https://github.com/krahets/hello-algo.git
synced 2026-10-21 21:45:19 +08:00
* placeholder * 0413更新 * 0413删除comments * 第四章第一节进行一对一修改,发现几个核心问题,后续不再重复叙述。具体问题参见具体说明 因为该版本不会被用在最后的校对输出中,后续不再复制纸质版原文,只标注出现不一致的部分 1.非程序部分大量出现文本和纸质版不一致,已全部修改,AI翻译版本没有纸质版自然 2.程序部分python部分就和纸质版不一致,但是其他程序和AI翻译的python一致,建议让AI先搬运纸质版python的翻译,再加工其他的部分,翻译水平尚可。 3.程序部分AI翻译版本不稳定,大部分使用动宾结构表达,少部分使用动名词结构,出现少量语法错误。纸质版大多使用动名词结构 4.相对中文在线版格式未出现问题,该部分表现较好 * 第四章后续章节未精确修改和俄语版不一致的部分,因为从第一节可以看出,AI基本上都是自己翻的。 在这里标出了所有应该修改的部分 除了第一节以外新增的问题: 1.程序部分出现语法错误 2.单复数问题在程序和标题中出现多次,正文中更是屡见不鲜 3.程序部分,python错了之后,后续的程序全部参照python一起错,建议统一修改,还是参照python但是一起修改掉 * 第十章第一节整体检查 1.正文部分和纸质版的重叠仍然基本没有,都是AI翻译 2.程序部分除了python和纸质版翻译不一致以外,出现较为严重的术语错误 3.接下来的四节内容我将不再展示正文部分的不一致,以上已经可以充分说明了 * 第十章后续章节梳理(该部分仍然绝大部分的内容都和纸质版完全不一致,从措辞到表达方法都不一致,包括正文也包括程序) 考虑到还需要进行AI设计,在此没有重复标注 其他问题: 1.正文中有一些细节,比如说引号的删除、大小写的确认、 2.程序上也出现了术语错误和语法错误 * docs(ru): refine wording after sentence-level Chinese comparison --------- Co-authored-by: Veronica <[email protected]> Co-authored-by: krahets <[email protected]>
92 lines
9.2 KiB
Markdown
92 lines
9.2 KiB
Markdown
# Двоичный поиск точки вставки
|
|
|
|
Двоичный поиск можно использовать не только для поиска целевого элемента, но и для решения многих вариаций задачи, например для поиска позиции вставки целевого элемента.
|
|
|
|
## Случай без повторяющихся элементов
|
|
|
|
!!! question
|
|
|
|
Дан упорядоченный массив `nums` длины $n$ и элемент `target` , причем в массиве нет повторяющихся элементов. Нужно вставить `target` в массив `nums` , сохранив порядок. Если элемент `target` уже присутствует в массиве, вставьте его слева от него. Верните индекс, который будет иметь `target` после вставки. Пример показан на рисунке ниже.
|
|
|
|

|
|
|
|
Если мы хотим переиспользовать код двоичного поиска из предыдущего раздела, нужно ответить на два вопроса.
|
|
|
|
**Вопрос 1**: если массив содержит `target` , будет ли индекс вставки совпадать с индексом этого элемента?
|
|
|
|
По условию `target` нужно вставить слева от равного элемента, а это означает, что новый `target` занимает место старого `target` . Иначе говоря, **если массив содержит `target` , то индекс вставки совпадает с индексом этого `target`**.
|
|
|
|
**Вопрос 2**: если массив не содержит `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` .
|
|
|
|

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

|
|
|
|
=== "<3>"
|
|

|
|
|
|
=== "<4>"
|
|

|
|
|
|
=== "<5>"
|
|

|
|
|
|
=== "<6>"
|
|

|
|
|
|
=== "<7>"
|
|

|
|
|
|
=== "<8>"
|
|

|
|
|
|
Если посмотреть на следующий код, то видно, что действия в ветвях `nums[m] > target` и `nums[m] == target` совпадают, поэтому эти две ветви можно объединить.
|
|
|
|
Даже в этом случае можно оставить условия развернутыми, потому что так логика выглядит более ясной и код легче читать.
|
|
|
|
```src
|
|
[file]{binary_search_insertion}-[class]{}-[func]{binary_search_insertion}
|
|
```
|
|
|
|
!!! tip
|
|
|
|
Код в этом разделе записан в стиле «двойного замкнутого интервала». При желании можно самостоятельно реализовать вариант «слева закрыт, справа открыт».
|
|
|
|
Если смотреть в целом, суть двоичного поиска сводится к тому, что для указателей $i$ и $j$ заранее задаются ориентиры поиска. Целью может быть конкретный элемент, например `target` , а может быть и диапазон элементов, например все элементы, меньшие `target` .
|
|
|
|
В ходе непрерывного двоичного деления указатели $i$ и $j$ постепенно приближаются к заранее заданной цели. В конце они либо успешно находят ответ, либо останавливаются после выхода за границы.
|