You are given an array of integers heights where heights[i] represents the height of a bar. The width of each bar is 1.
Return the area of the largest rectangle that can be formed among the bars.
Note: This chart is known as a histogram.
Example 1:
Input: heights = [7,1,7,2,2,4]
Output: 8Example 2:
Input: heights = [1,3,7]
Output: 7Constraints:
1 <= heights.length <= 1000.0 <= heights[i] <= 1000
You should aim for a solution with O(n) time and O(n) space, where n is the size of the input array.
A rectangle has a height and a width. Can you visualize how rectangles are formed in the given input? Considering one bar at a time might help. We can try to form rectangles by going through every bar and current bar's height will be the height of the rectangle. How can you determine the width of the rectangle for the current bar being the height of the rectangle? Extending the current bar to the left and right might help determine the rectangle's width.
For a bar with height h, try extending it to the left and right. We can see that we can't extend further when we encounter a bar with a smaller height than h. The width will be the number of bars within this extended range. A brute force solution would be to go through every bar and find the area of the rectangle it can form by extending towards the left and right. This would be an O(n^2) solution. Can you think of a better way? Maybe precomputing the left and right boundaries might be helpful.
The left and right boundaries are the positions up to which we can extend the bar at index i. The area of the rectangle will be height[i] * (right - left + 1), which is the general formula for height * width. These boundaries are determined by the first smaller bars encountered to the left and right of the current bar. How can we find the left and right boundaries now? Maybe a data structure is helpful.
We can use a stack with a monotonically strictly increasing nature, but instead of storing values, we store indices in the stack and perform operations based on the values at those indices. The top of the stack will represent the smaller bar that we encounter while extending the current bar. To find the left and right boundaries, we perform this algorithm from left to right and vice versa, storing the boundaries. Then, we iterate through the array to find the area for each bar and return the maximum area we get.
For every bar, we try to treat it as the shortest bar in the rectangle.
To find how wide this rectangle can extend, we look left and right until we hit a bar shorter than the current one.
The width between these two boundaries gives the largest rectangle where this bar is the limiting height.
We repeat this for every bar and keep track of the maximum rectangle found.
maxArea store the largest rectangle found.i:height be the height of the current bar.height.height.maxArea with height * width.maxArea.class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
maxArea = 0
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
maxArea = max(maxArea, height * (rightMost - leftMost + 1))
return maxAreaA large rectangle in the histogram must have some bar as its shortest bar.
If we know the index of the minimum height bar in any range, then:
So we can solve the problem with a divide and conquer idea:
[L, R], find the index of the smallest bar.[L, minIndex - 1].[minIndex + 1, R].[L, R] is the maximum of those three.To find the index of the minimum height quickly for any range, we use a segment tree built on heights. It supports “min index in range” queries in O(log n) time, which makes the whole divide-and-conquer efficient.
solve(L, R):L > R, return 0.L == R, return heights[L] (only one bar).minIndex = index of the smallest bar in [L, R].area_with_min = heights[minIndex] * (R - L + 1)area_left = solve(L, minIndex - 1)area_right = solve(minIndex + 1, R)max(area_with_min, area_left, area_right).solve(0, n - 1) where n is the number of bars.class MinIdx_Segtree:
def __init__(self, N, A):
self.n = N
self.INF = int(1e9)
self.A = A
while (self.n & (self.n - 1)) != 0:
self.A.append(self.INF)
self.n += 1
self.tree = [0] * (2 * self.n)
self.build()
def build(self):
for i in range(self.n):
self.tree[self.n + i] = i
for j in range(self.n - 1, 0, -1):
a = self.tree[j << 1]
b = self.tree[(j << 1) + 1]
if self.A[a] <= self.A[b]:
self.tree[j] = a
else:
self.tree[j] = b
def update(self, i, val):
self.A[i] = val
j = (self.n + i) >> 1
while j >= 1:
a = self.tree[j << 1]
b = self.tree[(j << 1) + 1]
if self.A[a] <= self.A[b]:
self.tree[j] = a
else:
self.tree[j] = b
j >>= 1
def query(self, ql, qh):
return self._query(1, 0, self.n - 1, ql, qh)
def _query(self, node, l, h, ql, qh):
if ql > h or qh < l:
return self.INF
if l >= ql and h <= qh:
return self.tree[node]
a = self._query(node << 1, l, (l + h) >> 1, ql, qh)
b = self._query((node << 1) + 1, ((l + h) >> 1) + 1, h, ql, qh)
if a == self.INF:
return b
if b == self.INF:
return a
return a if self.A[a] <= self.A[b] else b
class Solution:
def getMaxArea(self, heights, l, r, st):
if l > r:
return 0
if l == r:
return heights[l]
minIdx = st.query(l, r)
return max(max(self.getMaxArea(heights, l, minIdx - 1, st),
self.getMaxArea(heights, minIdx + 1, r, st)),
(r - l + 1) * heights[minIdx])
def largestRectangleArea(self, heights):
n = len(heights)
st = MinIdx_Segtree(n, heights)
return self.getMaxArea(heights, 0, n - 1, st)For each bar, we want to know how far it can stretch left and right before bumping into a shorter bar.
That distance tells us the widest rectangle where this bar is the limiting height.
To efficiently find the nearest smaller bar on both sides, we use a monotonic stack that keeps indices of bars in increasing height order.
This lets us compute boundaries in linear time instead of checking outward for every bar.
i, the nearest smaller bar on the left:-1.n.height[i] * (right - left - 1)class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
stack = []
leftMost = [-1] * n
for i in range(n):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
leftMost[i] = stack[-1]
stack.append(i)
stack = []
rightMost = [n] * n
for i in range(n - 1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
if stack:
rightMost[i] = stack[-1]
stack.append(i)
maxArea = 0
for i in range(n):
leftMost[i] += 1
rightMost[i] -= 1
maxArea = max(maxArea, heights[i] * (rightMost[i] - leftMost[i] + 1))
return maxAreaWe want, for each bar, the widest area where it can act as the shortest bar.
With a single pass and a stack, we can do this on the fly:
Each bar is pushed and popped at most once, giving an efficient, one-pass solution.
(start_index, height),maxArea = 0.i and height h:start = i.h:(index, height) from the stack.maxArea with height * (i - index).start = index (the new bar can start from here).(start, h) onto the stack.(index, height) in the stack:maxArea with height * (n - index), where n is the total number of bars.maxArea.class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
maxArea = 0
stack = [] # pair: (index, height)
for i, h in enumerate(heights):
start = i
while stack and stack[-1][1] > h:
index, height = stack.pop()
maxArea = max(maxArea, height * (i - index))
start = index
stack.append((start, h))
for i, h in stack:
maxArea = max(maxArea, h * (len(heights) - i))
return maxAreaWe want, for every bar, to know how wide it can stretch while still being the shortest bar in that rectangle.
A monotonic stack helps with this:
To make sure every bar eventually gets popped and processed, we run the loop one extra step with a “virtual” bar of height 0 at the end.
Each bar is pushed and popped at most once, so this is both optimal and clean.
maxArea = 0i from 0 to n (inclusive):i == n (we’re past the last bar, acting like height 0), orheights[i] is less than or equal to the height at the top index of the stack:h.i (it extends from 0 to i - 1).i - stack.top() - 1.maxArea with h * width.i onto the stack.maxArea holds the largest rectangle area. Return it.class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
maxArea = 0
stack = []
for i in range(n + 1):
while stack and (i == n or heights[stack[-1]] >= heights[i]):
height = heights[stack.pop()]
width = i if not stack else i - stack[-1] - 1
maxArea = max(maxArea, height * width)
stack.append(i)
return maxArea