LeetCode #84 Hard

Largest Rectangle in Histogram

Given bar heights, find the largest rectangle that fits under the histogram.

stackmonotonic-stack
Open on LeetCode ↗
02

Intuition

The best rectangle using bar i as its height extends to the nearest shorter bar on each side. A monotonic increasing stack finds both boundaries in one pass: when a bar pops, the popper is its right boundary and the new stack top its left.

How to spot this pattern

A monotonic stack shows up when every element needs to know its nearest smaller (or greater) neighbour on each side. Keeping indices in increasing height order means the moment a shorter bar arrives, everything taller is finalised — the new bar is its right boundary and the stack entry below is its left. Next-greater-element, daily-temperatures and maximal-rectangle all run on this engine.

03

Approach

1

Every rectangle is pinned by a bar

The optimal rectangle's height equals some bar's full height — so compute, for each bar, its widest reach.

2

Pop = settle

Keep indices of increasing heights. When h[i] < top, the top pops: its width is i − (new top) − 1, area = height × width. Settle all taller bars.

3

Sentinel flush

Append a 0-height bar to force every index to settle by the end. Each bar pushes and pops once → O(n).

04

Solution & live demo

1class Solution:
2 def largestRectangleArea(self, heights):
3 stack, best = [], 0
4 for i, h in enumerate(heights + [0]): # sentinel flush
5 while stack and heights[stack[-1]] > h:
6 height = heights[stack.pop()]
7 left = stack[-1] if stack else -1
8 best = max(best, height * (i - left - 1))
9 stack.append(i)
10 return best
05

Common pitfalls

Forgetting the sentinel and leaving bars unprocessed

✗ Wrong
for i, h in enumerate(heights):
    ...
✓ Right
for i, h in enumerate(heights + [0]):
    ...

On an increasing histogram like [1, 2, 3] nothing ever pops, so no rectangle is ever measured and the answer comes back 0. Appending a zero-height bar is shorter than everything, forcing the stack to drain through the normal code path instead of a duplicated post-loop block.

Computing the width from the popped index

✗ Wrong
best = max(best, height * (i - stack[-1]))
✓ Right
left = stack[-1] if stack else -1
best = max(best, height * (i - left - 1))

The rectangle spans the gap strictly between its two boundaries, so the width is i - left - 1, not i - left. And when the stack empties, the bar extends all the way to the start, which is what the -1 sentinel encodes.

Popping with >= instead of >

✗ Wrong
while stack and heights[stack[-1]] >= h:
✓ Right
while stack and heights[stack[-1]] > h:

For equal heights the earlier bar's true right boundary lies further right, so settling it now measures it short. It still gets the right final answer — the last of the equal run spans the full width — but only by accident, and the reasoning stops holding under small changes.

06

Edge cases

Increasing histogram

Nothing settles until the sentinel; widths then span from each bar to the end.

Empty stack after a pop

Left boundary is −1 → width is the full prefix, handled by the ternary.

07

Complexity

Time
O(n)
Space
O(n)
One push and one pop per bar.