Problem
We are given an array heights, where heights[i] represents the height of a histogram bar. Each bar has a width of 1.
We need to return the area of the largest rectangle that can be formed using one or more consecutive bars.
For example:
heights = [7, 1, 7, 2, 2, 4]The answer is:
8Because the largest rectangle can be formed using bars of height 2 across width 4.
Brute Force Approach
The first idea is to treat each bar as the minimum height of a rectangle.
For every index i, we take heights[i] as the rectangle height. Then we expand to the left and right while the neighboring bars are at least this tall.
from typing import List
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
maxArea = 0
n = len(heights)
for i in range(n):
height = heights[i]
rightMost = i + 1
while rightMost < n and heights[rightMost] >= height:
rightMost += 1
leftMost = i
while leftMost >= 0 and heights[leftMost] >= height:
leftMost -= 1
rightMost -= 1
leftMost += 1
area = height * (rightMost - leftMost + 1)
maxArea = max(maxArea, area)
return maxAreaFor each bar, we scan left and right to find how far the rectangle can extend.
Time Complexity
For each index, we may scan the whole array.
O(n^2)Space Complexity
O(1)This works, but it can be slow for larger inputs.
Why we need +1 in
area = height * (rightMost - leftMost + 1)The +1 is needed because both leftMost and rightMost are inclusive indices.
After these adjustments:
rightMost -= 1
leftMost += 1both variables point to bars that are actually part of the rectangle.
So the width is:
rightMost - leftMost + 1Example
Suppose the rectangle spans indices:
Index: 0 1 2 3 4
Bars: █ █ █
^ ^
leftMost rightMostHere,
leftMost = 1
rightMost = 3The rectangle covers:
- index 1
- index 2
- index 3
That’s 3 bars, not:
3 - 1 = 2because subtraction only tells you the distance between the endpoints.
You need:
width = 3 - 1 + 1 = 3Why do we subtract and add first?
Notice your search loops stop one step too far:
while rightMost < n and heights[rightMost] >= height:
rightMost += 1When the loop ends, rightMost is either:
- the first smaller bar, or
n
So you move back one position:
rightMost -= 1Similarly,
while leftMost >= 0 and heights[leftMost] >= height:
leftMost -= 1When this loop ends, leftMost is the first smaller bar (or -1), so you move forward:
leftMost += 1Now both leftMost and rightMost are included in the rectangle, which is why the width formula is:
width = rightMost - leftMost + 1
area = height * widthA good rule to remember is:
- Inclusive range (
leftandrightare both included):right - left + 1 - Exclusive range (
rightis one past the end, like Python slicing):right - left
In your brute-force solution, you’re working with an inclusive range, so the +1 is required.
Optimized Approach: Monotonic Stack
We can solve this more efficiently using a monotonic increasing stack.
The stack stores pairs:
(index, height)The idea is:
- Keep bars in increasing height order.
- When we find a smaller bar, it means the previous taller bars cannot extend any further to the right.
- So we pop those taller bars and calculate their rectangle area.
- The popped bar’s start index is reused because the current shorter bar can extend back to that position.
from typing import List
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
maxArea = 0
stack = []
for currIndex, currHeight in enumerate(heights):
start = currIndex
while stack and currHeight < stack[-1][1]:
index, height = stack.pop()
maxArea = max(maxArea, height * (currIndex - index))
start = index
stack.append((start, currHeight))
for i, h in stack:
maxArea = max(maxArea, h * (len(heights) - i))
return maxAreaWhy This Works
When the current height is smaller than the height at the top of the stack, the taller bar cannot continue further.
So we calculate its area immediately:
height * (currIndex - index)Here:
heightis the height of the rectangle.currIndex - indexis the width.indexis where this height started.
After popping, we update:
start = indexThis allows the current shorter bar to extend further left.
At the end, some bars may still remain in the stack. These bars can extend until the end of the histogram, so we calculate their areas separately.
Example Walkthrough
For:
heights = [7, 1, 7, 2, 2, 4]When we see 1, we know the previous 7 cannot extend anymore, so we calculate:
7 * 1 = 7Later, when we see 2, the previous 7 also cannot continue, so we calculate:
7 * 1 = 7The bars with height 2 can extend across multiple positions, giving:
2 * 4 = 8So the final answer is:
8Complexity
Time Complexity
O(n)Each bar is pushed into the stack once and popped at most once.
Space Complexity
O(n)In the worst case, the stack may store all bars.
Final Thoughts
The brute force solution is easier to understand because it directly checks how far each bar can expand.
The optimized monotonic stack solution is more efficient because it avoids repeated scanning. It calculates the largest possible rectangle for each bar exactly when that bar can no longer extend to the right.