Largest Rectangle In Histogram

Question

Given an array of integersĀ heightsĀ representing the histogram's bar height where the width of each bar isĀ 1, returnĀ the area of the largest rectangle in the histogram.

Example 1:

Pasted image 20240515172344.png

Input: heights = [2,1,5,6,2,3]
Output: 10
Explanation: The above is a histogram where width of each bar is 1.
The largest rectangle is shown in the red area, which has an area = 10 units.

Example 2:

Pasted image 20240515172409.png

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

Solution

Bruteforce

Since a histogram is bounded by it's min height. A bruteforce solution is to go through everything to calculate the are with subject to min height:

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        maxArea = float('-inf')

        for i in range(len(heights)):
            minHeight = heights[i]
        
            for j in range(i, len(heights)):
                minHeight = min(minHeight, heights[j])
                width = j - i + 1
                maxArea = max(maxArea, minHeight * width)
        
        return maxArea

Time Complexity: $O(n^2)$
Space: $O(1)$

Monotonic stack O(n)

In this problem, we can use Monotonic Stack to solve this algorithm. Basically we want to build an increasing histogram.

For example, the following is considered an increasing histogram:

Pasted image 20240515172832.png

It basically just increasing, not going down.

Why?

Because once we have something like that, we can calculate the maxArea from each the histogram using just for loop:

increasingHistograms = [1,5,6]
maxArea = 0

for i, height in enumerate(increasingHistograms):
    maxWidth = len(increasingHistograms) - i
    maxArea = max(maxArea, maxWidth * height)

print(maxArea) # 10

So how do we do it.

We need to pop out the one that's not increasing so that we have an increasing histogram.

Let's consider this one:

Pasted image 20240515173241.png

We want to pop out (2,5,6) as we go from left -> right so that our histogram is increasing.

Pasted image 20240515173330.png

As a result, a histogram of 1, 2, 3 should be increasing.

But what happen to the empty space after we pop? We should extend the rest of our histogram out to use that space.

Pasted image 20240515173517.png

So that's the idea. In the end we can just calculate the maxArea of the graph above to keep track of our maxArea

How ever, we also need to calculate the maxArea of the increasingHistograms that we popped

Algorithm walk through

Pasted image 20240515173823.png

We start at i = 0 at 2. We include 2 as an increasingHistograms.

Pasted image 20240515173959.png

Moving on to 1, since 1 < 2 we cannot form an increasingHistograms, we have to remove 2.

When removing 2, we make sure to calculate the maxArea of 2 which is 2

As we remove 2, we also expands 1 out because of the empty space we can use

Pasted image 20240515174130.png

Now at 5, since 5 is an increasingHistograms we continue

Pasted image 20240515174200.png

Similar to 6, it's increasing.

Pasted image 20240515174217.png

Now at 2, since 6 > 2, it's not increasing anymore. We have to pop 6 out and calculate the maxArea of 6.

So we have maxArea = 6

Pasted image 20240515174329.png

Since 5 > 2, we have to pop 5 out as well. As a result, we want to calculate the maxArea of 5. Note that from 5 we can technically expands to 2

Pasted image 20240515174440.png

So the maxArea = 10, we pop 5 out and expand 2

Pasted image 20240515174521.png

Now at 3, we can add it in our histogram that's fine.

At the end, we want to calculate the area of the above histogram manually.

Implementation

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        maxArea = 0
        increasingHistograms = []
        
        for i, height in enumerate(heights):
            startIndex = i
            
            while increasingHistograms and self.cannotFormIncreasingHistogram(increasingHistograms, height):
                topStackIndex, topStackHeight = increasingHistograms.pop()
                maxArea = max(maxArea, topStackHeight * (i - topStackIndex))
                startIndex = topStackIndex
                
            increasingHistograms.append((startIndex, height))
        
        for i, height in increasingHistograms:
            maxWidth = len(heights) - i
            maxArea = max(maxArea, height * maxWidth)
        
        
        return maxArea

    
    def cannotFormIncreasingHistogram(self, stack: List[Tuple[int, int]], height: int) -> bool:
        topStackHeight = stack[-1][1]
        return height < topStackHeight

Time complexity: $O(n)$
Space complexity: $O(n)$