Largest Rectangle in Histogram
Problem (restated)
Bars of unit width with heights heights[i]. Return the area of the largest rectangle contained in the histogram.
Intuition
For each bar, the widest rectangle that uses it as the shortest bar spans until the first strictly shorter bar on the left and on the right. A monotonic increasing stack finds those bounds in amortized O(1) per bar.
Approaches
Increasing stack + sentinel flush
Tested onlyIdea. Stack of indices with strictly increasing heights. When a lower bar arrives (or a virtual height-0 at the end), pop and compute height * (right - left - 1) where right = i and left is the new top (or -1).
Walkthrough. [2,1,5,6,2,3] → largest is height 5 width 2 = 10 (bars at indices 2-3), overall answer 10.
Trade-offs. Two-pass “nearest smaller left/right” arrays also work at O(n) but use more memory and code. Brute force is O(n²).
export function largestRectangleArea(heights: number[]): number {
const n = heights.length;
const stack: number[] = []; // increasing heights by index
let best = 0;
for (let i = 0; i <= n; i++) {
const h = i === n ? 0 : heights[i]!;
while (stack.length && h < heights[stack[stack.length - 1]!]!) {
const height = heights[stack.pop()!]!;
const left = stack.length ? stack[stack.length - 1]! : -1;
const width = i - left - 1;
best = Math.max(best, height * width);
}
stack.push(i);
}
return best;
}
export function largestRectangleArea(heights: number[]): number {
const n = heights.length;
const stack: number[] = []; // increasing heights by index
let best = 0;
for (let i = 0; i <= n; i++) {
const h = i === n ? 0 : heights[i]!;
while (stack.length && h < heights[stack[stack.length - 1]!]!) {
const height = heights[stack.pop()!]!;
const left = stack.length ? stack[stack.length - 1]! : -1;
const width = i - left - 1;
best = Math.max(best, height * width);
}
stack.push(i);
}
return best;
}
Template connection
Monotonic stack for nearest smaller bounds (increasing stack). Contrast with next-greater (decreasing stack) in 496/503/739.
Expand per bar
Tested onlyIdea. For each index as the shortest bar, expand left/right while bars are at least as tall; area = h * width.
Trade-offs. Easy to derive; too slow for large n. The stack version is the O(n) upgrade.
/** O(n²): for each bar expand left/right while height >= h[i]. */
export function largestRectangleAreaBrute(heights: number[]): number {
let best = 0;
const n = heights.length;
for (let i = 0; i < n; i++) {
let lo = i, hi = i;
while (lo > 0 && heights[lo - 1]! >= heights[i]!) lo--;
while (hi + 1 < n && heights[hi + 1]! >= heights[i]!) hi++;
best = Math.max(best, heights[i]! * (hi - lo + 1));
}
return best;
}
/** O(n²): for each bar expand left/right while height >= h[i]. */
export function largestRectangleAreaBrute(heights: number[]): number {
let best = 0;
const n = heights.length;
for (let i = 0; i < n; i++) {
let lo = i, hi = i;
while (lo > 0 && heights[lo - 1]! >= heights[i]!) lo--;
while (hi + 1 < n && heights[hi + 1]! >= heights[i]!) hi++;
best = Math.max(best, heights[i]! * (hi - lo + 1));
}
return best;
}
Deep dive
Histogram bars form a skyline. For each bar i, the largest rectangle with height[i] as the shortest bar stretches from the previous smaller bar on the left to the next smaller on the right. A monotonic increasing stack of indices finds those bounds in one pass: when you pop i, the new top is the previous smaller, and the current index is the next smaller.
Area = height[mid] * (right - left - 1). Run a sentinel 0 at the end so every bar is popped.
Reflection
- Which pattern gave this away within 90 seconds?
- What changed from the standard template?
- What would break the current solution?