Largest Rectangle in Histogram

This one looks like a drawing puzzle but it is really a stack puzzle. Many people get stuck because they try every rectangle by hand. The interviewer wants to see if you can spot when a stack turns a slow scan into a fast one. So this question is a classic test of the monotonic stack idea.

🎯 The Problem

You get a list of bar heights and must find the biggest rectangle that fits fully inside the bars.

  • Each bar has a width of 1. Think of it like a bar chart.
  • A rectangle is a flat block resting inside the bars.
  • Its height is limited by the shortest bar it covers.
  • Its width is how many bars it stretches across.
  • The answer is the largest area, where area is height times width.
Input: heights = [2, 1, 5, 6, 2, 3]
Output: 10
Explanation: bars at index 2 and 3 have heights 5 and 6.
The shortest of them is 5, and the width is 2 bars.
So area = 5 * 2 = 10, which is the largest possible.

Here is the bar chart for that input. Notice how bars 5 and 6 sit next to each other and form the tall block.

idx0 h=2

idx1 h=1

idx2 h=5

idx3 h=6

idx4 h=2

idx5 h=3

Best rectangle: height 5 across idx2 and idx3 = area 10

🐒 Approach 1: Try Every Range (Brute Force)

Pick a start bar, slide right, and track the shortest bar as the height.

The idea:

  • Loop a start bar over every position.
  • Loop an end bar from the start to the right.
  • Track the shortest bar seen so far in that range. That is the height.
  • The bar count is the width. Area is height times width. Keep the biggest.

Why it is weak:

  • For every start you scan all the way to the end.
  • That is two nested loops over n bars.
  • Time is O(nΒ²). It rescans the same bars again and again.

Here is the try-every-range code:

largest_rectangle_histogram_brute_force.py
def largest_rectangle_area(heights):
best = 0
for left in range(len(heights)):
smallest = heights[left]
for right in range(left, len(heights)):
smallest = min(smallest, heights[right])
best = max(best, smallest * (right - left + 1))
return best

⚑ Approach 2: Monotonic Increasing Stack (Best)

The idea in one line: for each bar, find the widest stretch where that bar is the shortest one, using a stack that only grows in height.

The idea:

  • For each bar ask: how far left and right can it stretch before hitting a shorter bar?
  • A monotonic increasing stack is a stack whose heights only go up from bottom to top.
  • Store indexes in it, not heights, so widths can be measured later.

How it works:

  • Walk the bars left to right.
  • While the bar on top of the stack is taller than the current bar, pop it. That taller bar can stretch no further right.
  • On a pop, the current index is the right edge. The new stack top is the left edge.
  • Width is the gap between those edges. Height is the popped bar. Multiply for the area and keep the best.
  • Add a fake bar of height 0 at the very end. It is shorter than everything, so it forces every leftover bar to pop and get measured.

Why it is fast:

  • Each bar is pushed once and popped once.
  • So the whole scan is O(n).
  • A shorter bar settles the rectangles for all taller bars behind it at once.

Here is the stack changing as we scan [2, 1, 5, 6, 2, 3]. Watch how popping a taller bar gives us a finished rectangle.

push idx0 h=2 -> stack [0]

idx1 h=1 shorter: pop idx0, area 2*1=2 -> push idx1, stack [1]

push idx2 h=5 -> stack [1,2]

push idx3 h=6 -> stack [1,2,3]

idx4 h=2 shorter: pop idx3 area 6*1=6, pop idx2 area 5*2=10 -> push idx4, stack [1,4]

push idx5 h=3 -> stack [1,4,5]

end bar h=0: pop idx5 area 3*1=3, pop idx4 area 2*3=6, pop idx1 area 1*6=6

Largest area found = 10

Steps to Solve

  1. Create an empty stack that will hold bar indexes.
  2. Walk through the bars from left to right. Treat the position after the last bar as a fake bar of height 0.
  3. While the stack is not empty and the bar at the top is taller than the current bar, pop the top.
  4. For the popped bar, its height is its own height. Its width reaches from the new stack top up to the current index.
  5. Compute area as height times width and update the best area.
  6. After popping, push the current index onto the stack.
  7. When the scan ends, the best area is the answer.

This Python version uses a list as the stack and appends the fake 0 bar by looping one step past the end.

largest_rectangle.py
def largest_rectangle_area(heights):
stack = [] # holds indexes
n = len(heights)
best = 0
for i in range(n + 1):
cur = 0 if i == n else heights[i] # fake 0 bar at the end
while stack and heights[stack[-1]] > cur:
height = heights[stack.pop()] # popped bar's height
left_edge = stack[-1] if stack else -1
width = i - left_edge - 1 # bars between edges
best = max(best, height * width)
stack.append(i) # push current index
return best
heights = [2, 1, 5, 6, 2, 3]
print(largest_rectangle_area(heights))

The output of the above code will be:

10

Let us walk through the Python version line by line so the stack logic is clear.

stack = [] starts an empty list that will hold indexes. We store indexes, not heights, because we need the index to measure how wide a rectangle is.

for i in range(n + 1): loops one step past the end. That extra step is where the fake bar lives.

cur = 0 if i == n else heights[i] sets the current height. On the very last step i equals n, so we use 0. That 0 is shorter than every real bar. It forces the stack to empty out and measure every bar left inside.

while stack and heights[stack[-1]] > cur: checks the bar on top of the stack. If that top bar is taller than the current bar, the top bar cannot stretch any further right. So we must measure it now.

height = heights[stack.pop()] pops the top index and reads its height. This popped bar is the height of the rectangle we are about to measure.

left_edge = stack[-1] if stack else -1 looks at the new top of the stack. That is the nearest shorter bar on the left. If the stack is empty, nothing blocks us on the left, so the left edge is -1.

width = i - left_edge - 1 counts the bars strictly between the left edge and the current index i. The current index is the right edge. So the rectangle covers everything between, and that is its width.

best = max(best, height * width) keeps the largest area we have seen.

stack.append(i) pushes the current index. After all taller bars are popped, the stack stays increasing, which is exactly the monotonic property we rely on.

⏱️ Time and Space Complexity

The brute force scans every range, so it costs O(nΒ²) time but almost no extra memory. The monotonic stack pushes and pops each index exactly once. So the whole scan is O(n) time. The stack can hold up to n indexes, so it needs O(n) extra memory. You trade a little memory for a big speed gain.

Approach Time Complexity Space Complexity
Brute force (all ranges) O(nΒ²) O(1)
Monotonic increasing stack O(n) O(n)

Tip

The fake 0 bar at the end is the cleanest trick here. It guarantees every bar still in the stack gets popped and measured, so you do not need a separate cleanup loop after the main scan.

🧩 Key Takeaways

  • βœ… Each bar is the limiting height for the widest block where it is the shortest bar.
  • βœ… A monotonic increasing stack stores indexes whose right edge is not known yet.
  • βœ… When a shorter bar appears, pop the taller bars and measure their rectangles right then.
  • βœ… Width is the gap between the new stack top and the current index, so store indexes, not heights.
  • βœ… Adding a fake 0 bar at the end clears the stack and measures every leftover bar.

Check Your Knowledge

4 questions Show quiz Hide quiz

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does the Largest Rectangle in Histogram problem ask for?

    Why: You return the biggest rectangle area that fits fully inside the bar chart.

  2. 2

    What kind of stack does the optimal solution use?

    Why: It uses a monotonic increasing stack that holds indexes so widths can be measured.

  3. 3

    When we pop a bar, how is its rectangle width found?

    Why: The width is the gap between the left edge (new stack top) and the right edge (current index).

  4. 4

    Why add a fake bar of height 0 at the end?

    Why: The 0 bar is shorter than all real bars, so it empties the stack and measures every leftover bar.

πŸš€ What’s Next?