Наибольший прямоугольник в гистограмме
Условие
Дан массив высот столбцов гистограммы (ширина каждого столбца = 1). Найдите площадь наибольшего прямоугольника, который можно вписать в гистограмму.
Пример
largest_rectangle([2, 1, 5, 6, 2, 3]) → 10
Объяснение: прямоугольник с высотой 5 и шириной 2 (столбцы 5 и 6)
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Наибольший прямоугольник в гистограмме
Задача
Дан массив высот столбцов гистограммы (ширина каждого столбца = 1). Требуется найти максимальную площадь прямоугольника, который можно вписать в гистограмму.
Для примера largest_rectangle([2, 1, 5, 6, 2, 3]):
- Прямоугольник с высотой 5 и шириной 2 (столбцы с индексами 2 и 3) имеет площадь 5 × 2 = 10
- Это максимальная площадь
Решение 1: Stack (Оптимальное, O(n))
Используем монотонный стек для отслеживания высот в возрастающем порядке.
Ключевая идея:
- Когда встречаем высоту меньше вершины стека, начинаем вычислять площади
- Для каждой высоты h, ширина прямоугольника определяется расстоянием до предыдущей более низкой высоты
def largest_rectangle(heights):
"""
Находит наибольший прямоугольник в гистограмме за O(n).
Идея:
- Стек хранит индексы высот в возрастающем порядке
- Когда встречаем меньшую высоту, начинаем вычислять площади
- Для каждой высоты определяем максимальную ширину
"""
if not heights:
return 0
stack = [] # Хранит индексы
max_area = 0
for i in range(len(heights)):
# Пока текущая высота меньше высоты на вершине стека
while stack and heights[stack[-1]] > heights[i]:
# Вычисляем площадь с высотой stack.pop()
h_index = stack.pop()
h = heights[h_index]
# Определяем ширину
# Справа: i (текущий индекс)
# Слева: stack[-1] если есть, иначе -1
width = i if not stack else i - stack[-1] - 1
area = h * width
max_area = max(max_area, area)
stack.append(i)
# Обработаем оставшиеся элементы в стеке
while stack:
h_index = stack.pop()
h = heights[h_index]
width = len(heights) if not stack else len(heights) - stack[-1] - 1
area = h * width
max_area = max(max_area, area)
return max_area
# Тестирование
print(largest_rectangle([2, 1, 5, 6, 2, 3])) # 10
print(largest_rectangle([2, 4])) # 4
print(largest_rectangle([2, 1, 2])) # 2
print(largest_rectangle([0, 9])) # 9
Сложность:
- Временная: O(n) — каждый элемент добавляется и удаляется один раз
- Пространственная: O(n) — размер стека в худшем случае
Пошаговое объяснение
Для heights = [2, 1, 5, 6, 2, 3]:
i=0: heights[0]=2
stack = [0]
i=1: heights[1]=1
heights[1]=1 < heights[0]=2
Вычисляем: h=2, width=1 (от начала до текущего), area=2×1=2
stack = [1]
i=2: heights[2]=5
heights[2]=5 > heights[1]=1
stack = [1, 2]
i=3: heights[3]=6
heights[3]=6 > heights[2]=5
stack = [1, 2, 3]
i=4: heights[4]=2
heights[4]=2 < heights[3]=6
Вычисляем h=6: width=1 (от 2 до 4), area=6×1=6
Вычисляем h=5: width=2 (от 1 до 4), area=5×2=10 ← МАКСИМУМ
heights[4]=2 == heights[1]=1
stack = [1, 4]
i=5: heights[5]=3
heights[5]=3 > heights[4]=2
stack = [1, 4, 5]
Обработка оставшихся:
h=3: width=1, area=3×1=3
h=2: width=4 (от начала 0 до конца 6), area=2×4=8
h=1: width=6, area=1×6=6
Максимум: 10 ✓
Решение 2: Divide & Conquer (O(n log n))
Рекурсивно делим гистограмму по минимальной высоте.
def largest_rectangle_divide(heights):
"""
Используется разделение и покорение.
Сложность: O(n log n) в среднем, O(n²) в худшем.
"""
def helper(left, right):
if left > right:
return 0
# Находим минимальную высоту
min_index = left
for i in range(left, right + 1):
if heights[i] < heights[min_index]:
min_index = i
# Площадь прямоугольника с минимальной высотой
area = heights[min_index] * (right - left + 1)
# Рекурсивно вычисляем для левой и правой частей
area = max(area, helper(left, min_index - 1))
area = max(area, helper(min_index + 1, right))
return area
return helper(0, len(heights) - 1)
print(largest_rectangle_divide([2, 1, 5, 6, 2, 3])) # 10
Сложность: O(n log n) в среднем, O(n²) в худшем
Решение 3: Брутфорс (O(n²))
Для каждого индекса находим максимальную ширину.
def largest_rectangle_brute(heights):
"""
Простой подход: для каждой высоты найти максимальную ширину.
Сложность: O(n²)
"""
if not heights:
return 0
max_area = 0
for i in range(len(heights)):
# Ищем влево
left = i
while left > 0 and heights[left - 1] >= heights[i]:
left -= 1
# Ищем вправо
right = i
while right < len(heights) - 1 and heights[right + 1] >= heights[i]:
right += 1
# Вычисляем площадь
width = right - left + 1
area = heights[i] * width
max_area = max(max_area, area)
return max_area
print(largest_rectangle_brute([2, 1, 5, 6, 2, 3])) # 10
Сложность: O(n²) — медленно
Решение 4: Оптимизированный Брутфорс (O(n²) с лучшей константой)
def largest_rectangle_optimized(heights):
"""
Брутфорс с предварительно вычисленными границами.
Сложность: O(n²), но с меньшей константой.
"""
if not heights:
return 0
n = len(heights)
left = [0] * n
right = [0] * n
# Вычисляем для каждого элемента индекс первого меньшего слева
left[0] = -1
for i in range(1, n):
j = i - 1
while j >= 0 and heights[j] >= heights[i]:
j = left[j]
left[i] = j
# Вычисляем для каждого элемента индекс первого меньшего справа
right[n - 1] = n
for i in range(n - 2, -1, -1):
j = i + 1
while j < n and heights[j] >= heights[i]:
j = right[j]
right[i] = j
# Вычисляем максимальную площадь
max_area = 0
for i in range(n):
width = right[i] - left[i] - 1
area = heights[i] * width
max_area = max(max_area, area)
return max_area
print(largest_rectangle_optimized([2, 1, 5, 6, 2, 3])) # 10
Сравнение подходов
| Метод | Временная | Пространственная | Описание |
|---|---|---|---|
| Stack | O(n) | O(n) | Оптимальное, используй это |
| Divide & Conquer | O(n log n) | O(log n) | Хорошо в среднем |
| Оптимизированный BF | O(n²) | O(n) | Работает быстрее обычного |
| Простой BF | O(n²) | O(n) | Просто, но медленно |
Обобщение: Maximal Rectangle
Этот алгоритм базис для более сложной задачи — максимальный прямоугольник в матрице:
def maximal_rectangle(matrix):
"""
Находит максимальный прямоугольник в матрице.
Идея: для каждой строки вычисляем высоты и применяем largest_rectangle.
"""
if not matrix:
return 0
rows = len(matrix)
cols = len(matrix[0])
heights = [0] * cols
max_area = 0
for row in range(rows):
# Обновляем высоты
for col in range(cols):
heights[col] = heights[col] + 1 if matrix[row][col] == 1 else 0
# Применяем алгоритм гистограммы
max_area = max(max_area, largest_rectangle(heights))
return max_area
Ключевые моменты
- Монотонный стек — основная техника
- Ширина прямоугольника = расстояние между индексами меньших элементов
- O(n) сложность достигается благодаря тому, что каждый элемент обрабатывается дважды
- Применимо к другим задачам со стеком (валидные скобки, нарастающий подмассив)
Заключение
Stack метод — классическое решение для задачи наибольшего прямоугольника в гистограмме. Это O(n) алгоритм, который часто встречается на интервью у компаний наподобие Google, Facebook, Amazon. Ключевой навык — понимание монотонного стека.