You should aim for a solution with O(n) time and O(n) space, where n is the size of the input array.
Hint 1
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.
Hint 2
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.
Hint 3
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.
Hint 4
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.
Company Tags
Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
Monotonic Stack - Using a stack to efficiently track elements in increasing/decreasing order
Next Smaller Element Pattern - Finding the nearest smaller element on left and right for each position
Divide and Conquer - Breaking problems into subproblems based on minimum elements (for segment tree approach)
Array Traversal - Iterating through arrays while maintaining state
1. Brute Force
Intuition
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.
Algorithm
Let maxArea store the largest rectangle found.
For each index i:
Let height be the height of the current bar.
Expand to the right until you find a bar shorter than height.
Expand to the left while bars are not shorter than height.
publicclassSolution{publicintLargestRectangleArea(int[] heights){int n = heights.Length;int maxArea =0;for(int i =0; i < n; i++){int height = heights[i];int rightMost = i +1;while(rightMost < n && heights[rightMost]>= height){
rightMost++;}int leftMost = i;while(leftMost >=0&& heights[leftMost]>= height){
leftMost--;}
rightMost--;
leftMost++;
maxArea = Math.Max(maxArea, height *(rightMost - leftMost +1));}return maxArea;}}
funclargestRectangleArea(heights []int)int{
n :=len(heights)
maxArea :=0for i :=0; i < n; i++{
height := heights[i]
rightMost := i +1for rightMost < n && heights[rightMost]>= height {
rightMost++}
leftMost := i
for leftMost >=0&& heights[leftMost]>= height {
leftMost--}
rightMost--
leftMost++
area := height *(rightMost - leftMost +1)if area > maxArea {
maxArea = area
}}return maxArea
}
class Solution {funlargestRectangleArea(heights: IntArray): Int {val n = heights.size
var maxArea =0for(i in0 until n){val height = heights[i]var rightMost = i +1while(rightMost < n && heights[rightMost]>= height){
rightMost++}var leftMost = i
while(leftMost >=0&& heights[leftMost]>= height){
leftMost--}
rightMost--
leftMost++
maxArea =maxOf(maxArea, height *(rightMost - leftMost +1))}return maxArea
}}
classSolution{funclargestRectangleArea(_ heights:[Int])->Int{let n = heights.count
var maxArea =0for i in0..<n {let height = heights[i]var rightMost = i +1while rightMost < n && heights[rightMost]>= height {
rightMost +=1}var leftMost = i
while leftMost >=0&& heights[leftMost]>= height {
leftMost -=1}
rightMost -=1
leftMost +=1
maxArea =max(maxArea, height *(rightMost - leftMost +1))}return maxArea
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(1)
2. Divide And Conquer (Segment Tree)
Intuition
A 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:
The largest rectangle that uses that bar as the limiting height spans the entire range.
Anything larger must lie entirely on the left or entirely on the right of that bar.
So we can solve the problem with a divide and conquer idea:
In a given range [L, R], find the index of the smallest bar.
Compute:
The area using this bar across the whole range.
The best area entirely in [L, minIndex - 1].
The best area entirely in [minIndex + 1, R].
The answer for [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.
Algorithm
Build a segment tree over the heights array:
Each node stores the index of the minimum height in its segment.
Define a recursive function solve(L, R):
If L > R, return 0.
If L == R, return heights[L] (only one bar).
Use the segment tree to find minIndex = index of the smallest bar in [L, R].
Compute:
area_with_min = heights[minIndex] * (R - L + 1)
area_left = solve(L, minIndex - 1)
area_right = solve(minIndex + 1, R)
Return max(area_with_min, area_left, area_right).
The final answer is solve(0, n - 1) where n is the number of bars.
classMinIdx_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()defbuild(self):for i inrange(self.n):
self.tree[self.n + i]= i
for j inrange(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
defupdate(self, i, val):
self.A[i]= val
j =(self.n + i)>>1while 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 >>=1defquery(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
classSolution:defgetMaxArea(self, heights, l, r, st):if l > r:return0if l == r:return heights[l]
minIdx = st.query(l, r)returnmax(max(self.getMaxArea(heights, l, minIdx -1, st),
self.getMaxArea(heights, minIdx +1, r, st)),(r - l +1)* heights[minIdx])deflargestRectangleArea(self, heights):
n =len(heights)
st = MinIdx_Segtree(n, heights)return self.getMaxArea(heights,0, n -1, st)
publicclassMinIdx_Segtree{int n;finalintINF=(int)1e9;int[]A, tree;publicMinIdx_Segtree(intN,int[] heights){this.n =N;this.A= heights;while(Integer.bitCount(n)!=1){A=java.util.Arrays.copyOf(A, n +1);A[n]=INF;
n++;}
tree =newint[2* n];build();}publicvoidbuild(){for(int i =0; i < n; i++){
tree[n + i]= i;}for(int j = n -1; j >=1; j--){int a = tree[j <<1];int b = tree[(j <<1)+1];if(A[a]<=A[b]){
tree[j]= a;}else{
tree[j]= b;}}}publicvoidupdate(int i,int val){A[i]= val;for(int j =(n + i)>>1; j >=1; j >>=1){int a = tree[j <<1];int b = tree[(j <<1)+1];if(A[a]<=A[b]){
tree[j]= a;}else{
tree[j]= b;}}}publicintquery(int ql,int qh){returnquery(1,0, n -1, ql, qh);}publicintquery(int node,int l,int h,int ql,int qh){if(ql > h || qh < l)returnINF;if(l >= ql && h <= qh)return tree[node];int a =query(node <<1, l,(l + h)>>1, ql, qh);int b =query((node <<1)+1,((l + h)>>1)+1, h, ql, qh);if(a ==INF)return b;if(b ==INF)return a;returnA[a]<=A[b]? a : b;}}publicclassSolution{publicintgetMaxArea(int[] heights,int l,int r,MinIdx_Segtree st){if(l > r)return0;if(l == r)return heights[l];int minIdx = st.query(l, r);returnMath.max(Math.max(getMaxArea(heights, l, minIdx -1, st),getMaxArea(heights, minIdx +1, r, st)),(r - l +1)* heights[minIdx]);}publicintlargestRectangleArea(int[] heights){int n = heights.length;MinIdx_Segtree st =newMinIdx_Segtree(n, heights);returngetMaxArea(heights,0, n -1, st);}}
classMinIdx_Segtree{public:int n;constint INF =1e9;
vector<int> A;
vector<int> tree;MinIdx_Segtree(int N, vector<int>& a){this->n = N;this->A = a;while(__builtin_popcount(n)!=1){
A.push_back(INF);
n++;}
tree.resize(2* n);build();}voidbuild(){for(int i =0; i < n; i++){
tree[n + i]= i;}for(int j = n -1; j >=1; j--){int a = tree[j<<1];int b = tree[(j<<1)+1];if(A[a]<=A[b])tree[j]=a;else tree[j]= b;}}voidupdate(int i,int val){
A[i]= val;for(int j =(n + i)>>1; j >=1; j >>=1){int a = tree[j<<1];int b = tree[(j<<1)+1];if(A[a]<=A[b])tree[j]=a;else tree[j]= b;}}intquery(int ql,int qh){returnquery(1,0, n -1, ql, qh);}intquery(int node,int l,int h,int ql,int qh){if(ql > h || qh < l)return INF;if(l >= ql && h <= qh)return tree[node];int a =query(node <<1, l,(l + h)>>1, ql, qh);int b =query((node <<1)+1,((l + h)>>1)+1, h, ql, qh);if(a==INF)return b;if(b==INF)return a;return A[a]<=A[b]?a:b;}};classSolution{public:intgetMaxArea(vector<int>& heights,int l,int r, MinIdx_Segtree& st){if(l > r)return0;if(l == r)return heights[l];int minIdx = st.query(l, r);returnmax(max(getMaxArea(heights, l, minIdx -1, st),getMaxArea(heights, minIdx +1, r, st)),(r - l +1)* heights[minIdx]);}intlargestRectangleArea(vector<int>& heights){int n = heights.size();
MinIdx_Segtree st(n, heights);returngetMaxArea(heights,0, n -1, st);}};
classMinIdx_Segtree{/**
* @param {number} N
* @param {number[]} heights
*/constructor(N, heights){this.n =N;this.INF=1e9;this.A= heights.slice();while((this.n &(this.n -1))!==0){this.A.push(this.INF);this.n++;}this.tree =newArray(2*this.n).fill(0);this.build();}build(){for(let i =0; i <this.n; i++){this.tree[this.n + i]= i;}for(let j =this.n -1; j >=1; j--){let a =this.tree[j <<1];let b =this.tree[(j <<1)+1];this.tree[j]=this.A[a]<=this.A[b]? a : b;}}/**
* @param {number} i
* @param {number} val
*/update(i, val){this.A[i]= val;for(let j =(this.n + i)>>1; j >=1; j >>=1){let a =this.tree[j <<1];let b =this.tree[(j <<1)+1];this.tree[j]=this.A[a]<=this.A[b]? a : b;}}/**
* @param {number} ql
* @param {number} qh
* @return {number}
*/query(ql, qh){returnthis._query(1,0,this.n -1, ql, qh);}_query(node, l, h, ql, qh){if(ql > h || qh < l)returnthis.INF;if(l >= ql && h <= qh)returnthis.tree[node];let a =this._query(node <<1, l,(l + h)>>1, ql, qh);let b =this._query((node <<1)+1,((l + h)>>1)+1, h, ql, qh);if(a ===this.INF)return b;if(b ===this.INF)return a;returnthis.A[a]<=this.A[b]? a : b;}}classSolution{/**
* @param {number[]} heights
* @return {number}
*/largestRectangleArea(heights){const n = heights.length;const st =newMinIdx_Segtree(n, heights);returnthis.getMaxArea(heights,0, n -1, st);}/**
* @param {number[]} heights
* @param {number} l
* @param {number} r
* @param {MinIdx_Segtree} st
* @return {number}
*/getMaxArea(heights, l, r, st){if(l > r)return0;if(l === r)return heights[l];const minIdx = st.query(l, r);return Math.max(this.getMaxArea(heights, l, minIdx -1, st),this.getMaxArea(heights, minIdx +1, r, st),(r - l +1)* heights[minIdx],);}}
publicclassMinIdx_Segtree{privateint n;privatereadonlyint INF =(int)1e9;privateint[] A, tree;publicMinIdx_Segtree(int N,int[] heights){this.n = N;this.A =newint[heights.Length];
heights.CopyTo(this.A,0);while((n &(n -1))!=0){
Array.Resize(ref A, n +1);
A[n]= INF;
n++;}
tree =newint[2* n];Build();}privatevoidBuild(){for(int i =0; i < n; i++){
tree[n + i]= i;}for(int j = n -1; j >=1; j--){int a = tree[j <<1];int b = tree[(j <<1)+1];
tree[j]= A[a]<= A[b]? a : b;}}publicintQuery(int ql,int qh){returnQuery(1,0, n -1, ql, qh);}privateintQuery(int node,int l,int h,int ql,int qh){if(ql > h || qh < l)return INF;if(l >= ql && h <= qh)return tree[node];int a =Query(node <<1, l,(l + h)>>1, ql, qh);int b =Query((node <<1)+1,((l + h)>>1)+1, h, ql, qh);if(a == INF)return b;if(b == INF)return a;return A[a]<= A[b]? a : b;}}publicclassSolution{publicintLargestRectangleArea(int[] heights){int n = heights.Length;MinIdx_Segtree st =newMinIdx_Segtree(n, heights);returnGetMaxArea(heights,0, n -1, st);}privateintGetMaxArea(int[] heights,int l,int r,MinIdx_Segtree st){if(l > r)return0;if(l == r)return heights[l];int minIdx = st.Query(l, r);return Math.Max(
Math.Max(GetMaxArea(heights, l, minIdx -1, st),GetMaxArea(heights, minIdx +1, r, st)),(r - l +1)* heights[minIdx]);}}
type MinIdxSegtree struct{
n int
INF int
A []int
tree []int}funcNewMinIdxSegtree(N int, A []int)*MinIdxSegtree {
st :=&MinIdxSegtree{
n: N,
INF:1000000000,
A:make([]int, N),}copy(st.A, A)for(st.n &(st.n -1))!=0{
st.A =append(st.A, st.INF)
st.n++}
st.tree =make([]int,2*st.n)
st.build()return st
}func(st *MinIdxSegtree)build(){for i :=0; i < st.n; i++{
st.tree[st.n+i]= i
}for j := st.n -1; j >0; j--{
a := st.tree[j<<1]
b := st.tree[(j<<1)+1]if st.A[a]<= st.A[b]{
st.tree[j]= a
}else{
st.tree[j]= b
}}}func(st *MinIdxSegtree)update(i, val int){
st.A[i]= val
j :=(st.n + i)>>1for j >=1{
a := st.tree[j<<1]
b := st.tree[(j<<1)+1]if st.A[a]<= st.A[b]{
st.tree[j]= a
}else{
st.tree[j]= b
}
j >>=1}}func(st *MinIdxSegtree)query(ql, qh int)int{return st.queryHelper(1,0, st.n-1, ql, qh)}func(st *MinIdxSegtree)queryHelper(node, l, h, ql, qh int)int{if ql > h || qh < l {return st.INF
}if l >= ql && h <= qh {return st.tree[node]}
a := st.queryHelper(node<<1, l,(l+h)>>1, ql, qh)
b := st.queryHelper((node<<1)+1,((l+h)>>1)+1, h, ql, qh)if a == st.INF {return b
}if b == st.INF {return a
}if st.A[a]<= st.A[b]{return a
}return b
}funcgetMaxArea(heights []int, l, r int, st *MinIdxSegtree)int{if l > r {return0}if l == r {return heights[l]}
minIdx := st.query(l, r)
area1 :=getMaxArea(heights, l, minIdx-1, st)
area2 :=getMaxArea(heights, minIdx+1, r, st)
area3 :=(r - l +1)* heights[minIdx]
maxArea := area1
if area2 > maxArea {
maxArea = area2
}if area3 > maxArea {
maxArea = area3
}return maxArea
}funclargestRectangleArea(heights []int)int{
n :=len(heights)
st :=NewMinIdxSegtree(n, heights)returngetMaxArea(heights,0, n-1, st)}
classMinIdxSegtree(privatevar n: Int, input: IntArray){privateval INF =1000000000privateval A: IntArray
privateval tree: IntArray
init{var size = n
while(size and(size -1)!=0) size++
A =IntArray(size){if(it < n) input[it]else INF }
n = size
tree =IntArray(2* n)for(i in0 until n) tree[n + i]= i
for(j in n -1 downTo 1){val left = tree[j shl1]val right = tree[j shl1or1]
tree[j]=if(A[left]<= A[right]) left else right
}}funquery(ql: Int, qh: Int): Int =queryHelper(1,0, n -1, ql, qh)privatefunqueryHelper(node: Int, l: Int, h: Int, ql: Int, qh: Int): Int {if(ql > h || qh < l)return INF
if(l >= ql && h <= qh)return tree[node]val mid =(l + h)shr1val a =queryHelper(node shl1, l, mid, ql, qh)val b =queryHelper((node shl1)+1, mid +1, h, ql, qh)returnwhen{
a == INF -> b
b == INF -> a
A[a]<= A[b]-> a
else-> b
}}}class Solution {funlargestRectangleArea(heights: IntArray): Int {val n = heights.size
val st =MinIdxSegtree(n, heights)fungetMaxArea(l: Int, r: Int): Int {if(l > r)return0if(l == r)return heights[l]val minIdx = st.query(l, r)val area =(r - l +1)* heights[minIdx]val leftArea =getMaxArea(l, minIdx -1)val rightArea =getMaxArea(minIdx +1, r)returnmaxOf(area, leftArea, rightArea)}returngetMaxArea(0, n -1)}}
classMinIdxSegmentTree{privatevar n:IntprivatevarINF=Int(1e9)privatevarA:[Int]privatevar tree:[Int]init(_N:Int,_A:[Int]){self.n =Nself.A=Awhile(self.n &(self.n -1))!=0{self.A.append(self.INF)self.n +=1}self.tree =[Int](repeating:0, count:2*self.n)build()}privatefuncbuild(){for i in0..<n {
tree[n + i]= i
}for j instride(from: n -1, through:1, by:-1){let a = tree[j <<1]let b = tree[(j <<1)+1]
tree[j]=(A[a]<=A[b])? a : b
}}funcupdate(_ i:Int,_ val:Int){A[i]= val
var j =(n + i)>>1while j >=1{let a = tree[j <<1]let b = tree[(j <<1)+1]
tree[j]=(A[a]<=A[b])? a : b
j >>=1}}funcquery(_ ql:Int,_ qh:Int)->Int{return_query(1,0, n -1, ql, qh)}privatefunc_query(_ node:Int,_ l:Int,_ h:Int,_ ql:Int,_ qh:Int)->Int{if ql > h || qh < l {returnINF}if l >= ql && h <= qh {return tree[node]}let a =_query(node <<1, l,(l + h)>>1, ql, qh)let b =_query((node <<1)+1,((l + h)>>1)+1, h, ql, qh)if a ==INF{return b }if b ==INF{return a }return(A[a]<=A[b])? a : b
}}classSolution{funcgetMaxArea(_ heights:[Int],_ l:Int,_ r:Int,_ st:MinIdxSegmentTree)->Int{if l > r {return0}if l == r {return heights[l]}let minIdx = st.query(l, r)returnmax(max(getMaxArea(heights, l, minIdx -1, st),getMaxArea(heights, minIdx +1, r, st)),(r - l +1)* heights[minIdx])}funclargestRectangleArea(_ heights:[Int])->Int{let n = heights.count
let st =MinIdxSegmentTree(n, heights)returngetMaxArea(heights,0, n -1, st)}}
Time & Space Complexity
Time complexity: O(nlogn)
Space complexity: O(n)
3. Stack
Intuition
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.
Algorithm
Use a stack to find, for each index i, the nearest smaller bar on the left:
If the current bar is shorter than the bar on top of the stack, pop until this is no longer true.
The top of the stack becomes the left boundary.
If the stack is empty, no smaller bar exists, so left boundary is -1.
Repeat the same process from right to left to find the nearest smaller bar on the right:
If no smaller bar exists, the right boundary is n.
For each bar:
Expand between the boundaries and compute its max rectangle: height[i] * (right - left - 1)
classSolution:deflargestRectangleArea(self, heights: List[int])->int:
maxArea =0
stack =[]# pair: (index, height)for i, h inenumerate(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 maxArea
We 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:
We keep a stack of indices where the bar heights are in increasing order.
As long as the next bar is taller or equal, we keep pushing indices.
When we see a shorter bar, it means the bar on top of the stack can’t extend further to the right:
We pop it and treat it as the height of a rectangle.
Its width goes from the new top of the stack + 1 up to the current index − 1.
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.
Algorithm
Initialize:
maxArea = 0
An empty stack to store indices of bars (with heights in increasing order).
Loop i from 0 to n (inclusive):
While the stack is not empty and either:
i == n (we're past the last bar, acting like height 0), or
heights[i] is less than or equal to the height at the top index of the stack:
Pop the top index; let its height be h.
Compute the width:
If the stack is empty, width = i (it extends from 0 to i - 1).
Otherwise, width = i - stack.top() - 1.
Update maxArea with h * width.
Push the current index i onto the stack.
After the loop, maxArea holds the largest rectangle area. Return it.
classSolution:deflargestRectangleArea(self, heights: List[int])->int:
n =len(heights)
maxArea =0
stack =[]for i inrange(n +1):while stack and(i == n or heights[stack[-1]]>= heights[i]):
height = heights[stack.pop()]
width = i ifnot stack else i - stack[-1]-1
maxArea =max(maxArea, height * width)
stack.append(i)return maxArea
publicclassSolution{publicintlargestRectangleArea(int[] heights){int n = heights.length;int maxArea =0;Stack<Integer> stack =newStack<>();for(int i =0; i <= n; i++){while(!stack.isEmpty()&&(i == n || heights[stack.peek()]>= heights[i])){int height = heights[stack.pop()];int width = stack.isEmpty()? i : i - stack.peek()-1;
maxArea =Math.max(maxArea, height * width);}
stack.push(i);}return maxArea;}}
classSolution{public:intlargestRectangleArea(vector<int>& heights){int n = heights.size();int maxArea =0;
stack<int> stack;for(int i =0; i <= n; i++){while(!stack.empty()&&(i == n || heights[stack.top()]>= heights[i])){int height = heights[stack.top()];
stack.pop();int width = stack.empty()? i : i - stack.top()-1;
maxArea =max(maxArea, height * width);}
stack.push(i);}return maxArea;}};
classSolution{/**
* @param {number[]} heights
* @return {number}
*/largestRectangleArea(heights){const n = heights.length;let maxArea =0;const stack =[];for(let i =0; i <= n; i++){while(
stack.length &&(i === n || heights[stack[stack.length -1]]>= heights[i])){const height = heights[stack.pop()];const width =
stack.length ===0? i : i - stack[stack.length -1]-1;
maxArea = Math.max(maxArea, height * width);}
stack.push(i);}return maxArea;}}
publicclassSolution{publicintLargestRectangleArea(int[] heights){int n = heights.Length;int maxArea =0;Stack<int> stack =newStack<int>();for(int i =0; i <= n; i++){while(stack.Count >0&&(i == n || heights[stack.Peek()]>= heights[i])){int height = heights[stack.Pop()];int width = stack.Count ==0? i : i - stack.Peek()-1;
maxArea = Math.Max(maxArea, height * width);}
stack.Push(i);}return maxArea;}}
funclargestRectangleArea(heights []int)int{
n :=len(heights)
maxArea :=0
stack :=make([]int,0)for i :=0; i <= n; i++{forlen(stack)>0&&(i == n || heights[stack[len(stack)-1]]>= heights[i]){
height := heights[stack[len(stack)-1]]
stack = stack[:len(stack)-1]
width := i
iflen(stack)>0{
width = i - stack[len(stack)-1]-1}
area := height * width
if area > maxArea {
maxArea = area
}}if i < n {
stack =append(stack, i)}}return maxArea
}
class Solution {funlargestRectangleArea(heights: IntArray): Int {val n = heights.size
var maxArea =0val stack = ArrayDeque<Int>()for(i in0..n){while(stack.isNotEmpty()&&(i == n || heights[stack.last()]>= heights[i])){val height = heights[stack.removeLast()]val width =if(stack.isEmpty()) i else i - stack.last()-1
maxArea =maxOf(maxArea, height * width)}if(i < n) stack.addLast(i)}return maxArea
}}
classSolution{funclargestRectangleArea(_ heights:[Int])->Int{let n = heights.count
var maxArea =0var stack =[Int]()for i in0...n {while!stack.isEmpty &&(i == n || heights[stack.last!]>= heights[i]){let height = heights[stack.removeLast()]let width = stack.isEmpty ? i : i - stack.last!-1
maxArea =max(maxArea, height * width)}
stack.append(i)}return maxArea
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
Common Pitfalls
Off-by-One Errors in Width Calculation
When computing the width of a rectangle, it is easy to miscalculate the boundaries. The width should be right - left - 1 when left and right are the indices of the nearest smaller bars (exclusive boundaries). Forgetting to subtract 1 or using inclusive boundaries incorrectly leads to wrong area calculations.
Forgetting to Process Remaining Stack Elements
After iterating through all bars, the stack may still contain indices of bars that never encountered a shorter bar to their right. These bars can extend all the way to the end of the histogram. Failing to pop and process these remaining elements misses potentially valid rectangles.
Using Strict vs Non-Strict Inequality
When comparing heights to decide whether to pop from the stack, using > versus >= matters. Using strict greater-than may cause bars of equal height to remain on the stack incorrectly, leading to wrong boundary calculations. The choice depends on how you handle the left boundary for bars with equal heights.
Incorrect Handling of Empty Stack
When the stack becomes empty while computing the left boundary, it means no shorter bar exists to the left. The width should extend from index 0 to the current position. Using stack[-1] or stack.top() on an empty stack causes runtime errors or incorrect results if not handled explicitly.
Not Handling Single-Element Arrays
Edge cases like arrays with a single bar or all bars having the same height require careful handling. The algorithm should correctly return the height of the single bar or the product of height and array length for uniform arrays without special-case bugs.