Files
hello-algo/ru/docs/chapter_greedy/max_capacity_problem.md
T
2026-01-20 15:08:42 +08:00

99 lines
9.1 KiB
Markdown

# Задача о максимальной вместимости
!!! question
Дан массив $ht$, каждый элемент которого представляет высоту вертикальной перегородки. Любые две перегородки в массиве и пространство между ними могут образовать контейнер.
Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина — разностью индексов двух перегородок в массиве.
Необходимо выбрать в массиве две перегородки так, чтобы вместимость образованного контейнера была максимальной, и вернуть максимальную вместимость. Пример показан на рисунке ниже.
![Пример данных для задачи о максимальной вместимости](../assets/max_capacity_example.png)
Контейнер образован любыми двумя перегородками, **поэтому состоянием в данной задаче являются индексы двух перегородок, обозначим их как $[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$ — длинная перегородка.
![Начальное состояние](../assets/max_capacity_initial_state.png)
Как показано на рисунке ниже, **если сейчас переместить длинную перегородку $j$ ближе к короткой перегородке $i$, вместимость обязательно уменьшится**.
Это происходит потому, что после перемещения длинной перегородки $j$ ширина $j-i$ определенно уменьшится; а высота определяется короткой перегородкой, поэтому высота может только остаться неизменной ($i$ остается короткой перегородкой) или уменьшиться (перемещенная $j$ становится короткой перегородкой).
![Состояние после перемещения длинной перегородки внутрь](../assets/max_capacity_moving_long_board.png)
Рассуждая в обратном направлении, **только сжимая короткую перегородку $i$ внутрь, мы можем увеличить вместимость**. Потому что хотя ширина определенно уменьшится, **высота может увеличиться** (перемещенная короткая перегородка $i$ может стать длиннее). Например, на рисунке ниже после перемещения короткой перегородки площадь увеличивается.
![Состояние после перемещения короткой перегородки внутрь](../assets/max_capacity_moving_short_board.png)
Таким образом, можно вывести жадную стратегию для данной задачи: инициализировать два указателя по краям контейнера, на каждом шаге сжимать внутрь указатель, соответствующий короткой перегородке, пока два указателя не встретятся.
На рисунке ниже показан процесс выполнения жадной стратегии.
1. В начальном состоянии указатели $i$ и $j$ находятся на противоположных концах массива.
2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость.
3. Сравнить высоты перегородок $i$ и $j$ и переместить короткую перегородку внутрь на одну позицию.
4. Циклически выполнять шаги `2.` и `3.`, пока $i$ и $j$ не встретятся.
=== "<1>"
![Жадный процесс решения задачи о максимальной вместимости](../assets/max_capacity_greedy_step1.png)
=== "<2>"
![max_capacity_greedy_step2](../assets/max_capacity_greedy_step2.png)
=== "<3>"
![max_capacity_greedy_step3](../assets/max_capacity_greedy_step3.png)
=== "<4>"
![max_capacity_greedy_step4](../assets/max_capacity_greedy_step4.png)
=== "<5>"
![max_capacity_greedy_step5](../assets/max_capacity_greedy_step5.png)
=== "<6>"
![max_capacity_greedy_step6](../assets/max_capacity_greedy_step6.png)
=== "<7>"
![max_capacity_greedy_step7](../assets/max_capacity_greedy_step7.png)
=== "<8>"
![max_capacity_greedy_step8](../assets/max_capacity_greedy_step8.png)
=== "<9>"
![max_capacity_greedy_step9](../assets/max_capacity_greedy_step9.png)
### Реализация кода
Код выполняет не более $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]
$$
![Пропущенные состояния при перемещении короткой перегородки](../assets/max_capacity_skipped_states.png)
При наблюдении обнаруживается, что **эти пропущенные состояния фактически являются всеми состояниями при перемещении длинной перегородки $j$ внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно приводит к уменьшению вместимости. То есть пропущенные состояния не могут быть оптимальным решением, **их пропуск не приведет к потере оптимального решения**.
Приведенный выше анализ показывает, что операция перемещения короткой перегородки является «безопасной», жадная стратегия эффективна.