LeetCode Β· #84

Largest Rectangle in Histogram Solution

Given an array of bar heights, find the area of the largest rectangle that can be formed within the histogram.

01
Section One Β· Problem

Problem Statement

🏷️

Difficulty

Hard

πŸ”—

LeetCode

Problem #84

πŸ—οΈ

Pattern

Monotonic Stack β€” increasing order

Given an array heights where heights[i] is the height of the i-th bar (width = 1), return the area of the largest rectangle that can be formed in the histogram.

Example
Input: heights = [2, 1, 5, 6, 2, 3] Output: 10 // Rectangle of height 5, width 2 (bars at index 2 and 3) β†’ 5Γ—2 = 10
Constraints
β€’ 1 ≀ heights.length ≀ 10⁡ β€’ 0 ≀ heights[i] ≀ 10⁴
02
Section Two Β· Approach 1

Brute Force β€” O(nΒ²)

For each bar, expand left and right to find the widest rectangle using that bar's height as the minimum. Area = height Γ— width.

Problem: O(n) expansion per bar β†’ O(nΒ²) total. A monotonic increasing stack determines the left and right boundaries for each bar in a single pass β€” when a bar is popped, we know exactly how far it extends.
Java β€” Brute Force
class Solution { public int largestRectangleArea(int[] heights) { int max = 0; for (int i = 0; i < heights.length; i++) { int minH = heights[i]; for (int j = i; j < heights.length; j++) { minH = Math.min(minH, heights[j]); max = Math.max(max, minH * (j - i + 1)); } } return max; } }
Python β€” Brute Force
class Solution: def largestRectangleArea(self, heights: list[int]) -> int: max_area = 0 for i in range(len(heights)): min_h = heights[i] for j in range(i, len(heights)): min_h = min(min_h, heights[j]) max_area = max(max_area, min_h * (j - i + 1)) return max_area
MetricValue
TimeO(nΒ²)
SpaceO(1)
03
Section Three Β· Approach 2

Monotonic Stack β€” O(n)

Maintain a stack of indices in increasing height order. When a bar is shorter than the stack's top, the taller bar can no longer extend right β€” pop it and compute its area. The width extends from the new stack top (left boundary) to the current index (right boundary).

πŸ’‘ Mental model: Imagine building towers from left to right. Each tower stands as tall as its bar. When you encounter a shorter bar, all taller towers to the left get "capped" β€” they can't extend further right. At that moment, calculate each capped tower's maximum rectangle: its height Γ— how far it spans. The stack remembers which towers are still "uncapped."
Algorithm
  • Step 1: Push indices onto stack while heights are non-decreasing.
  • Step 2: When heights[i] < heights[stack.peek()]: pop index top.
  • Step 3: Width = i βˆ’ stack.peek() βˆ’ 1 (or i if stack is empty). Area = heights[top] Γ— width.
  • Step 4: After scanning all bars, pop remaining stack entries using i = n as the right boundary.
🎯 Why increasing order?:
  • In an increasing stack, the bar at the top is always the tallest unresolved bar.
  • When a shorter bar arrives, we know the tall bar's rectangle can't extend further right (it's blocked).
  • We also know its left boundary β€” the previous stack entry. This gives us both boundaries in O(1).
04
Section Four Β· Trace

Visual Walkthrough

Trace through heights = [2, 1, 5, 6, 2, 3]:

Monotonic Increasing Stack β€” pop and compute area
HISTOGRAM: heights = [2, 1, 5, 6, 2, 3] 2 [0] 1 [1] 5 [2] 6 [3] 2 [4] 3 [5] ← max area = 5Γ—2 = 10 (bars [2..3], height limited to 5) STACK TRACE (increasing heights) i=0: push 0(h=2) stack: [0] i=1: h=1 < h[0]=2 β†’ pop 0. area=2Γ—1=2. push 1. stack: [1] maxArea=2 i=2: push 2. i=3: push 3. stack: [1,2,3] i=4: h=2 < h[3]=6 β†’ pop 3. area=6Γ—(4-2-1)=6. h=2 < h[2]=5 β†’ pop 2. area=5Γ—(4-1-1)=10 β˜… maxArea=10! push 4. stack: [1,4] i=5: push 5. stack: [1,4,5] Drain: pop 5β†’area=3. pop 4β†’area=8. pop 1β†’area=6. None exceeds 10. maxArea stays 10. Answer: 10 βœ“
05
Section Five Β· Implementation

Code β€” Java & Python

Java β€” Monotonic Stack
import java.util.Stack; class Solution { public int largestRectangleArea(int[] heights) { Stack<Integer> stack = new Stack<>(); int maxArea = 0; for (int i = 0; i <= heights.length; i++) { int h = (i == heights.length) ? 0 : heights[i]; // sentinel while (!stack.isEmpty() && h < heights[stack.peek()]) { int top = stack.pop(); int width = stack.isEmpty() ? i : i - stack.peek() - 1; maxArea = Math.max(maxArea, heights[top] * width); } stack.push(i); } return maxArea; } }
Python β€” Monotonic Stack
class Solution: def largestRectangleArea(self, heights: list[int]) -> int: stack = [] max_area = 0 for i in range(len(heights) + 1): h = heights[i] if i < len(heights) else 0 # sentinel while stack and h < heights[stack[-1]]: top = stack.pop() width = i if not stack else i - stack[-1] - 1 max_area = max(max_area, heights[top] * width) stack.append(i) return max_area
06
Section Six Β· Analysis

Complexity Analysis

ApproachTimeSpaceTrade-off
Brute ForceO(nΒ²)O(1)Expand left/right per bar
Divide & ConquerO(n log n)O(log n)Recursively split at min height
Monotonic Stack ← optimal O(n) O(n) Each bar pushed/popped once. Sentinel simplifies cleanup.
Sentinel trick:
  • By appending a height-0 bar at the end (using i == n), we force all remaining bars to be popped in the final iteration.
  • This eliminates the need for a separate cleanup loop after the main pass.
07
Section Seven Β· Edge Cases

Edge Cases & Pitfalls

CaseInputWhy It Matters
Single bar[5]Area = 5 Γ— 1 = 5.
All same height[3, 3, 3]Area = 3 Γ— 3 = 9. Stack stays sorted, drained at sentinel.
Ascending[1, 2, 3, 4]No pops until sentinel. Widest rectangle is minΓ—n or tallestΓ—1.
Descending[4, 3, 2, 1]Each bar pops the previous. Many small rectangles compared.
Contains zeros[2, 0, 2]Zero bar blocks extension. Rectangles on each side are independent.
⚠ Common Mistake: Getting the width calculation wrong when the stack is empty. If the stack is empty after popping, the popped bar's rectangle extends from index 0 to i-1 β€” width is i, not i - stack.peek() - 1. Always check stack.isEmpty() before peeking.

← Back to Stack problems