Files
hello-algo/ru/docs/chapter_greedy/max_capacity_problem.md
Yudong Jin 22b3b568ef fix Ru translation (#1894)
* docs(ru): replace prose quotes with guillemets

* docs(ru): replace prose semicolons with periods

* docs(ru): align animation title forms

* docs(ru): align figure and table references
2026-04-14 18:10:12 +08:00

9.0 KiB
Raw Blame History

Задача о максимальной вместимости

!!! 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>" max_capacity_greedy_step2

=== "<3>" max_capacity_greedy_step3

=== "<4>" max_capacity_greedy_step4

=== "<5>" max_capacity_greedy_step5

=== "<6>" max_capacity_greedy_step6

=== "<7>" max_capacity_greedy_step7

=== "<8>" max_capacity_greedy_step8

=== "<9>" max_capacity_greedy_step9

Код реализации

Цикл в коде выполняется не более n раз, поэтому временная сложность равна $O(n)$.

Переменные i, j, res используют дополнительную память постоянного размера, поэтому пространственная сложность равна $O(1)$.

[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 сдвигается внутрь. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно уменьшает вместимость. Иными словами, пропущенные состояния не могут быть оптимальным решением, поэтому их пропуск не приводит к потере оптимума.

Приведенный анализ показывает, что операция перемещения короткой перегородки является «безопасной», а жадная стратегия действительно корректна.