Largest Rectangle in Histogram (LeetCode 84)
You're given an array of integers heights, each entry the height of one histogram bar; every bar has width 1. Return the area of the largest rectangle you can draw that fits entirely inside the histogram's outline.
Worked Examples
heights = [2,1,5,6,2,3]10heights = [2,4]44 alone, or both bars at height 2, gives area 4.⚖️Formal Constraints & Bounds
1 <= heights.length <= 1050 <= heights[i] <= 104
Core Insight
Pushing the extended start index backwards when popping shorter bars accounts for full horizontal expansion.
Real-World Scenario & Production Applications
Maximal matrix rectangle detection in computer vision, silhouette bounding area extraction.
Step-by-Step Execution Trace Table
| Step | Bar Index i | Height h | Stack Action | Popped (idx, h) | Width () | Area | Running Max Area |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 2 | Push (0, 2) | - | - | - | 0 |
| 2 | 1 | 1 | Pop (0, 2), Push (0, 1) | (0, 2) | 2 | ||
| 3 | 2 | 5 | Push (2, 5) | - | - | - | 2 |
| 4 | 3 | 6 | Push (3, 6) | - | - | - | 2 |
| 5 | 4 | 2 | Pop (3, 6) | (3, 6) | 6 | ||
| Pop (2, 5), Push (2, 2) | (2, 5) | 10 | |||||
| 6 | 5 | 3 | Push (5, 3) | - | - | - | 10 |
| 7 | 6 (Sentinel) | 0 | Pop (5, 3) | (5, 3) | 10 | ||
| Pop (2, 2) | (2, 2) | 10 | |||||
| Pop (0, 1) | (0, 1) | 10 |
| 1 | extended_heights = heights + [0] |
| 2 | stack = [-1] |
| 3 | max_area = 0 |
| 4 | for i, h in enumerate(extended_heights): |
| 5 | while stack[-1] != -1 and extended_heights[stack[-1]] >= h: |
| 6 | popped_height = extended_heights[stack.pop()] |
| 7 | width = i - stack[-1] - 1 |
| 8 | max_area = max(max_area, popped_height * width) |
| 9 | stack.append(i) |
| 10 | return max_area |
Conceptual Narrative
Every candidate rectangle in a histogram is bounded by its shortest bar. A monotonic increasing stack finds each bar's left and right boundary limits, computing all maximal rectangles in O(N) time.
🧭 Conceptual Foundation & Pattern Intuition
For any bar with height , what is the largest rectangle that uses bar as its limiting height? The rectangle can extend horizontally to the left until it hits the first bar strictly shorter than (Nearest Smaller to Left, NSL), and to the right until it hits the first bar strictly shorter than (Nearest Smaller to Right, NSR). If NSL is at index and NSR is at index , the width is , and the area is . A Monotonic Increasing Stack computes this for all bars simultaneously:
- The stack stores indices of bars in strictly increasing height order.
- When an incoming bar is shorter than the bar at
stack[-1], bar serves as the NSR (right boundary) for the popped bar. - Once popped, the new stack top
stack[-1]is the NSL (left boundary) for that popped bar! - The Sentinel Trick: Appending a virtual bar of height
0at the end of the array guarantees that every single bar remaining on the stack is forced to pop and calculate its area, eliminating post-loop cleanup code. A second sentinel,-1at the bottom of the stack, is the left boundary of any bar with no shorter bar before it, so the width is alwaysi - stack[-1] - 1.
🏗️ The Analogy: Concrete Pillars Supporting an Overhead Roof
Imagine structural pillars lined up in a row. A bridge roof can rest across consecutive pillars at height , but only if every supporting pillar in that span is at least tall. As long as each new pillar is taller than the last, the potential roof height keeps climbing. But the moment a short pillar appears, it caps the horizontal span of all taller pillars before it! Those taller pillars are popped off the design clipboard, their maximal roof spans are calculated, and the shorter pillar becomes the new anchor.
🔑 Why It Works
The sentinel height 0 ensures that the final state leaves only the -1 sentinel on the stack, evaluating all possible rectangular spans in a clean, unified loop running in time and auxiliary memory.
Forgetting the sentinel 0 boundary: If the histogram is strictly increasing (e.g.
[1, 2, 3, 4]), no elements are ever popped during the loop. Appending a sentinel 0 (heights + [0]) or running a cleanup loop is mandatory to flush the stack.Incorrect width calculation for the leftmost bar: a popped bar with no shorter bar before it spans back to index 0. The
-1sentinel at the bottom of the stack makesi - stack[-1] - 1give that width (i); without it the width needs a separate empty-stack case.Storing heights instead of indices on the stack: Pushing bar heights instead of bar indices prevents determining how far left the rectangle can extend.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
"The area of the largest rectangle in the histogram": every rectangle is as tall as its lowest bar, so each bar's best rectangle spreads left and right until a lower bar stops it.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Decision rule: while extended_heights[stack[-1]] >= h, pop the top; its rectangle ends just before i on the right and just after the new stack[-1] on the left.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Measuring the width from the popped index (
i - popped_index) or before the pop: bars popped earlier also sat between the new top andi, so the left edge is the newstack[-1]after the pop. On[2, 1, 2]the popped-index width returns2instead of3.Dropping
stack[-1] != -1from thewhilecondition:extended_heights[-1]is the sentinel0, so a0bar pops the-1floor and the nextstack[-1]fails on an empty list.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I'd use a Histogram Stack. I keep bar indices on a stack with heights strictly increasing, and minus one at the bottom as the left wall. When a bar arrives that is not taller than the top, each bar I pop has found its right edge, the current index, and its left edge is the new top after the pop, the nearest lower bar on its left. So its widest rectangle is its height times i minus the new top minus one. The largest rectangle is the widest rectangle of its own lowest bar, so checking every bar once finds it. The trap is measuring the width from the popped index, which leaves out the bars popped earlier. A zero appended at the end pops whatever is left. Each index is pushed and popped once, so it is
O(N)time andO(N)space."
Complexity & Mathematical Proof
O(N)
Look at the execution steps: the for loop visits the N + 1 entries of extended_heights once. Each index is pushed once by stack.append(i) and popped at most once by stack.pop(), so the while loop runs at most N + 1 times over the whole scan, each time in O(1).
O(N) Auxiliary
Look at memory allocations: extended_heights is a copy of size N + 1 and stack holds at most N + 1 entries (the -1 included), so O(N). The output is one integer.
Derivation Progression
stack.append(i)
Every index of extended_heights is pushed exactly once: N + 1 pushes.
width = i - stack[-1] - 1
Each pop measures one bar's widest rectangle, popped_height * width, in O(1).
≤ 2(N + 1) total stack ops
An index can only be popped after it was pushed, so pops never outnumber pushes.
Variable Definitions
Total number of vertical bars in the histogram
Memory Architecture & Bounds
O(1) Iterative
O(N) extended_heights copy and the index stack
O(1) Max area integer
Boundary Best / Worst Cases
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
Triggers: "largest rectangle in the histogram". A bar of height can bound a rectangle horizontally as far left and right as adjacent bars remain , so its edges are the nearest lower bar on each side. The Histogram Stack finds both edges for every bar in one pass.
and , so an area is at most and fits in a 32-bit signed integer. Time budget: .
heights + [0] copies the whole input. On very large arrays, loop i from 0 to N and read h = heights[i] if i < N else 0: the same flush without the copy, and still without changing the caller's list.
Core Algorithmic State Invariants
- One Rectangle per Bar Is Enough
The largest rectangle has a lowest bar somewhere inside it, and that bar's widest rectangle is at least as large. Every index leaves stack exactly once and is measured when it leaves, so checking one rectangle per bar is guaranteed to find the answer.
- Both Nearest-Lower Neighbours in One Pass
At the moment a bar is popped, i is the nearest lower bar on its right and the new stack[-1] the nearest lower bar on its left. Any question about how far each value reaches before a smaller one stops it can reuse these two numbers, as in Maximum Subarray Min-Product (LC 1856).
Largest Rectangle in Histogram (LeetCode 84)
You're given an array of integers heights, each entry the height of one histogram bar; every bar has width 1. Return the area of the largest rectangle you can draw that fits entirely inside the histogram's outline.
Worked Examples
heights = [2,1,5,6,2,3]10heights = [2,4]44 alone, or both bars at height 2, gives area 4.⚖️Formal Constraints & Bounds
1 <= heights.length <= 1050 <= heights[i] <= 104
Core Insight
Pushing the extended start index backwards when popping shorter bars accounts for full horizontal expansion.
Real-World Scenario & Production Applications
Maximal matrix rectangle detection in computer vision, silhouette bounding area extraction.
Step-by-Step Execution Trace Table
| Step | Bar Index i | Height h | Stack Action | Popped (idx, h) | Width () | Area | Running Max Area |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 2 | Push (0, 2) | - | - | - | 0 |
| 2 | 1 | 1 | Pop (0, 2), Push (0, 1) | (0, 2) | 2 | ||
| 3 | 2 | 5 | Push (2, 5) | - | - | - | 2 |
| 4 | 3 | 6 | Push (3, 6) | - | - | - | 2 |
| 5 | 4 | 2 | Pop (3, 6) | (3, 6) | 6 | ||
| Pop (2, 5), Push (2, 2) | (2, 5) | 10 | |||||
| 6 | 5 | 3 | Push (5, 3) | - | - | - | 10 |
| 7 | 6 (Sentinel) | 0 | Pop (5, 3) | (5, 3) | 10 | ||
| Pop (2, 2) | (2, 2) | 10 | |||||
| Pop (0, 1) | (0, 1) | 10 |
| 1 | extended_heights = heights + [0] |
| 2 | stack = [-1] |
| 3 | max_area = 0 |
| 4 | for i, h in enumerate(extended_heights): |
| 5 | while stack[-1] != -1 and extended_heights[stack[-1]] >= h: |
| 6 | popped_height = extended_heights[stack.pop()] |
| 7 | width = i - stack[-1] - 1 |
| 8 | max_area = max(max_area, popped_height * width) |
| 9 | stack.append(i) |
| 10 | return max_area |
Conceptual Narrative
Every candidate rectangle in a histogram is bounded by its shortest bar. A monotonic increasing stack finds each bar's left and right boundary limits, computing all maximal rectangles in O(N) time.
🧭 Conceptual Foundation & Pattern Intuition
For any bar with height , what is the largest rectangle that uses bar as its limiting height? The rectangle can extend horizontally to the left until it hits the first bar strictly shorter than (Nearest Smaller to Left, NSL), and to the right until it hits the first bar strictly shorter than (Nearest Smaller to Right, NSR). If NSL is at index and NSR is at index , the width is , and the area is . A Monotonic Increasing Stack computes this for all bars simultaneously:
- The stack stores indices of bars in strictly increasing height order.
- When an incoming bar is shorter than the bar at
stack[-1], bar serves as the NSR (right boundary) for the popped bar. - Once popped, the new stack top
stack[-1]is the NSL (left boundary) for that popped bar! - The Sentinel Trick: Appending a virtual bar of height
0at the end of the array guarantees that every single bar remaining on the stack is forced to pop and calculate its area, eliminating post-loop cleanup code. A second sentinel,-1at the bottom of the stack, is the left boundary of any bar with no shorter bar before it, so the width is alwaysi - stack[-1] - 1.
🏗️ The Analogy: Concrete Pillars Supporting an Overhead Roof
Imagine structural pillars lined up in a row. A bridge roof can rest across consecutive pillars at height , but only if every supporting pillar in that span is at least tall. As long as each new pillar is taller than the last, the potential roof height keeps climbing. But the moment a short pillar appears, it caps the horizontal span of all taller pillars before it! Those taller pillars are popped off the design clipboard, their maximal roof spans are calculated, and the shorter pillar becomes the new anchor.
🔑 Why It Works
The sentinel height 0 ensures that the final state leaves only the -1 sentinel on the stack, evaluating all possible rectangular spans in a clean, unified loop running in time and auxiliary memory.
Forgetting the sentinel 0 boundary: If the histogram is strictly increasing (e.g.
[1, 2, 3, 4]), no elements are ever popped during the loop. Appending a sentinel 0 (heights + [0]) or running a cleanup loop is mandatory to flush the stack.Incorrect width calculation for the leftmost bar: a popped bar with no shorter bar before it spans back to index 0. The
-1sentinel at the bottom of the stack makesi - stack[-1] - 1give that width (i); without it the width needs a separate empty-stack case.Storing heights instead of indices on the stack: Pushing bar heights instead of bar indices prevents determining how far left the rectangle can extend.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
"The area of the largest rectangle in the histogram": every rectangle is as tall as its lowest bar, so each bar's best rectangle spreads left and right until a lower bar stops it.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Decision rule: while extended_heights[stack[-1]] >= h, pop the top; its rectangle ends just before i on the right and just after the new stack[-1] on the left.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Measuring the width from the popped index (
i - popped_index) or before the pop: bars popped earlier also sat between the new top andi, so the left edge is the newstack[-1]after the pop. On[2, 1, 2]the popped-index width returns2instead of3.Dropping
stack[-1] != -1from thewhilecondition:extended_heights[-1]is the sentinel0, so a0bar pops the-1floor and the nextstack[-1]fails on an empty list.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I'd use a Histogram Stack. I keep bar indices on a stack with heights strictly increasing, and minus one at the bottom as the left wall. When a bar arrives that is not taller than the top, each bar I pop has found its right edge, the current index, and its left edge is the new top after the pop, the nearest lower bar on its left. So its widest rectangle is its height times i minus the new top minus one. The largest rectangle is the widest rectangle of its own lowest bar, so checking every bar once finds it. The trap is measuring the width from the popped index, which leaves out the bars popped earlier. A zero appended at the end pops whatever is left. Each index is pushed and popped once, so it is
O(N)time andO(N)space."
Complexity & Mathematical Proof
O(N)
Look at the execution steps: the for loop visits the N + 1 entries of extended_heights once. Each index is pushed once by stack.append(i) and popped at most once by stack.pop(), so the while loop runs at most N + 1 times over the whole scan, each time in O(1).
O(N) Auxiliary
Look at memory allocations: extended_heights is a copy of size N + 1 and stack holds at most N + 1 entries (the -1 included), so O(N). The output is one integer.
Derivation Progression
stack.append(i)
Every index of extended_heights is pushed exactly once: N + 1 pushes.
width = i - stack[-1] - 1
Each pop measures one bar's widest rectangle, popped_height * width, in O(1).
≤ 2(N + 1) total stack ops
An index can only be popped after it was pushed, so pops never outnumber pushes.
Variable Definitions
Total number of vertical bars in the histogram
Memory Architecture & Bounds
O(1) Iterative
O(N) extended_heights copy and the index stack
O(1) Max area integer
Boundary Best / Worst Cases
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
Triggers: "largest rectangle in the histogram". A bar of height can bound a rectangle horizontally as far left and right as adjacent bars remain , so its edges are the nearest lower bar on each side. The Histogram Stack finds both edges for every bar in one pass.
and , so an area is at most and fits in a 32-bit signed integer. Time budget: .
heights + [0] copies the whole input. On very large arrays, loop i from 0 to N and read h = heights[i] if i < N else 0: the same flush without the copy, and still without changing the caller's list.
Core Algorithmic State Invariants
- One Rectangle per Bar Is Enough
The largest rectangle has a lowest bar somewhere inside it, and that bar's widest rectangle is at least as large. Every index leaves stack exactly once and is measured when it leaves, so checking one rectangle per bar is guaranteed to find the answer.
- Both Nearest-Lower Neighbours in One Pass
At the moment a bar is popped, i is the nearest lower bar on its right and the new stack[-1] the nearest lower bar on its left. Any question about how far each value reaches before a smaller one stops it can reuse these two numbers, as in Maximum Subarray Min-Product (LC 1856).