mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-12 03:40:58 +00:00
First version.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Задача о максимальной вместимости
|
||||
|
||||
!!! question
|
||||
|
||||
Дан массив $ht$, каждый элемент которого представляет высоту вертикальной перегородки. Любые две перегородки в массиве и пространство между ними могут образовать контейнер.
|
||||
|
||||
Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина — разностью индексов двух перегородок в массиве.
|
||||
|
||||
Необходимо выбрать в массиве две перегородки так, чтобы вместимость образованного контейнера была максимальной, и вернуть максимальную вместимость. Пример показан на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
Контейнер образован любыми двумя перегородками, **поэтому состоянием в данной задаче являются индексы двух перегородок, обозначим их как $[i, j]$**.
|
||||
|
||||
Согласно условию задачи, вместимость равна произведению высоты на ширину, где высота определяется короткой перегородкой, а ширина — разностью индексов двух перегородок в массиве. Обозначим вместимость как $cap[i, j]$, тогда можно получить формулу для расчета:
|
||||
|
||||
$$
|
||||
cap[i, j] = \min(ht[i], ht[j]) \times (j - i)
|
||||
$$
|
||||
|
||||
Пусть длина массива равна $n$, количество комбинаций двух перегородок (общее число состояний) составляет $C_n^2 = \frac{n(n - 1)}{2}$. Наиболее прямой подход — **перебрать все состояния**, чтобы найти максимальную вместимость, временная сложность составит $O(n^2)$.
|
||||
|
||||
### Определение жадной стратегии
|
||||
|
||||
Для этой задачи существует более эффективное решение. Как показано на рисунке ниже, выберем состояние $[i, j]$, которое удовлетворяет условию $i < j$ и $ht[i] < ht[j]$, то есть $i$ — короткая перегородка, $j$ — длинная перегородка.
|
||||
|
||||

|
||||
|
||||
Как показано на рисунке ниже, **если сейчас переместить длинную перегородку $j$ ближе к короткой перегородке $i$, вместимость обязательно уменьшится**.
|
||||
|
||||
Это происходит потому, что после перемещения длинной перегородки $j$ ширина $j-i$ определенно уменьшится; а высота определяется короткой перегородкой, поэтому высота может только остаться неизменной ($i$ остается короткой перегородкой) или уменьшиться (перемещенная $j$ становится короткой перегородкой).
|
||||
|
||||

|
||||
|
||||
Рассуждая в обратном направлении, **только сжимая короткую перегородку $i$ внутрь, мы можем увеличить вместимость**. Потому что хотя ширина определенно уменьшится, **высота может увеличиться** (перемещенная короткая перегородка $i$ может стать длиннее). Например, на рисунке ниже после перемещения короткой перегородки площадь увеличивается.
|
||||
|
||||

|
||||
|
||||
Таким образом, можно вывести жадную стратегию для данной задачи: инициализировать два указателя по краям контейнера, на каждом шаге сжимать внутрь указатель, соответствующий короткой перегородке, пока два указателя не встретятся.
|
||||
|
||||
На рисунке ниже показан процесс выполнения жадной стратегии.
|
||||
|
||||
1. В начальном состоянии указатели $i$ и $j$ находятся на противоположных концах массива.
|
||||
2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость.
|
||||
3. Сравнить высоты перегородок $i$ и $j$ и переместить короткую перегородку внутрь на одну позицию.
|
||||
4. Циклически выполнять шаги `2.` и `3.`, пока $i$ и $j$ не встретятся.
|
||||
|
||||
=== "<1>"
|
||||

|
||||
|
||||
=== "<2>"
|
||||

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

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

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

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

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

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

|
||||
|
||||
=== "<9>"
|
||||

|
||||
|
||||
### Реализация кода
|
||||
|
||||
Код выполняет не более $n$ итераций, **поэтому временная сложность составляет $O(n)$**.
|
||||
|
||||
Переменные $i$, $j$, $res$ используют константное дополнительное пространство, **поэтому пространственная сложность составляет $O(1)$**.
|
||||
|
||||
```src
|
||||
[file]{max_capacity}-[class]{}-[func]{max_capacity}
|
||||
```
|
||||
|
||||
### Доказательство корректности
|
||||
|
||||
Причина, по которой жадный алгоритм быстрее полного перебора, заключается в том, что каждый жадный выбор «пропускает» некоторые состояния.
|
||||
|
||||
Например, в состоянии $cap[i, j]$, где $i$ — короткая перегородка, $j$ — длинная перегородка. Если жадно переместить короткую перегородку $i$ внутрь на одну позицию, это приведет к «пропуску» состояний, показанных на рисунке ниже. **Это означает, что в дальнейшем невозможно будет проверить вместимость этих состояний**.
|
||||
|
||||
$$
|
||||
cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1]
|
||||
$$
|
||||
|
||||

|
||||
|
||||
При наблюдении обнаруживается, что **эти пропущенные состояния фактически являются всеми состояниями при перемещении длинной перегородки $j$ внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно приводит к уменьшению вместимости. То есть пропущенные состояния не могут быть оптимальным решением, **их пропуск не приведет к потере оптимального решения**.
|
||||
|
||||
Приведенный выше анализ показывает, что операция перемещения короткой перегородки является «безопасной», жадная стратегия эффективна.
|
||||
Reference in New Issue
Block a user