DSAPrep
HardStack

Largest Rectangle In Histogram

Given an array of integers heights representing a histogram's bar heights, where the width of each bar is 1, return the area of the largest rectangle that fits entirely within the histogram.

Example 1

Input: heights = [2,1,5,6,2,3]
Output: 10
Explanation: The largest rectangle has area 10, formed by the bars of height 5 and 6 (indices 2 and 3), taking the shorter height (5) across a width of 2.

Example 2

Input: heights = [2,4]
Output: 4

Constraints

  • 1 <= heights.length <= 10^5
  • 0 <= heights[i] <= 10^4
View original on LeetCode β†—

Brute Force (Expand Around Every Bar)

Time O(nΒ²)Space O(1)

The largest rectangle that uses bar i as its shortest bar spans as far left and right as neighboring bars stay at least as tall as heights[i]. So for every pair of bars (i, j), the tallest rectangle spanning that width is limited by the shortest bar in between. Track the running minimum height as the window expands.

class Solution:
def largestRectangleArea(self, heights: list[int]) -> int:
n = len(heights)
max_area = 0
for i in range(n):
min_height = heights[i]
for j in range(i, n):
min_height = min(min_height, heights[j])
max_area = max(max_area, min_height * (j - i + 1))
return max_area

Correct, but every pair (i, j) is checked explicitly β€” O(nΒ²) time, O(1) extra space.

Monotonic Stack

OptimalTime O(n)Space O(n)

Instead of checking every pair, notice each bar can only be the limiting (shortest) height of a rectangle that spans some contiguous range. For a fixed bar, that range stretches from the nearest shorter bar on its left to the nearest shorter bar on its right β€” everything in between is tall enough not to constrain it.

Scan left to right with a stack of (start_index, height) pairs, kept increasing in height from bottom to top. When the current bar is shorter than the top of the stack, that top bar’s rectangle can no longer extend any further right β€” pop it and finalize its area, using the current index as its right boundary. Its width extends back to start_index, since everything popped along the way was already absorbed into that same left boundary. After the scan, anything left on the stack extends all the way to the end of the array.

class Solution:
def largestRectangleArea(self, heights: list[int]) -> int:
stack = [] # (start_index, height), increasing height bottom to top
max_area = 0
for i, h in enumerate(heights):
start = i
while stack and stack[-1][1] >= h:
idx, height = stack.pop()
max_area = max(max_area, height * (i - idx))
start = idx
stack.append((start, h))
n = len(heights)
for idx, height in stack:
max_area = max(max_area, height * (n - idx))
return max_area

Tracing heights = [2,1,5,6,2,3] (stack items shown as height@start_index):

2@0
1 / 12
pushedinvalid

i=0, h=2: stack empty, push. stack = [2@0]

The largest rectangle found is area 10, from the bars of height 5 and 6 spanning width 2 β€” matching the expected output. Correctness: every bar is finalized exactly once, with its true left and right boundaries (the nearest strictly-shorter bars on each side), so no valid rectangle is missed. Complexity: each index is pushed once and popped at most once β†’ O(n) time despite the nested loop, O(n) space for the stack in the worst case (strictly increasing heights).