Skip to main content

Largest Rectangle in Histogram

Problem​

Given an array of integers heights representing the histogram's bar height where the width of each bar is 1, return the area of the largest rectangle in the histogram.

Constraints:
  • 1 <= heights.length <= 10⁵
  • 0 <= heights[i] <= 10⁴
Examples
Input: heights = [2, 1, 5, 6, 2, 3]
Output: 10
Explanation: The largest rectangle is shown in the red area, which has an area of 10 units.

84-largest-rectangle-area

Input: heights = [2, 4]
Output: 4
Explanation: The largest rectangle is shown in the red area, which has an area of 4 units.

84-largest-rectangle-area

Solution​

Whether using brute force or monotonic stack, the idea is to take a specific height, for example heights[2] (which is 5) in the first example, then expand in both directions to find the nearest bars that are shorter than it:

  • Expand left: heights[1] is less than heights[2], meaning to maintain the height of heights[2], the left boundary must be at index 1
  • Expand right: heights[4] is less than heights[2], meaning to maintain the height of heights[2], the right boundary must be at index 4

Thus, the maximum width maintaining the height heights[2] is 4 - 1 - 1 = 2.

84-largest-rectangle-area

For the brute force approach, we need to iterate through each height, then use it as a pivot and expand left and right to find the left and right boundaries. However, this results in O(n²) time complexity. Therefore, we can use a space-for-time strategy with a monotonic stack to pre-calculate the left and right boundaries for each bar. See the code comments for the specific implementation.

/**
* @param {number[]} heights
* @return {number}
*/
var largestRectangleArea = function (heights) {
const n = heights.length

// Find the left boundary (smaller than current element) for each bar
// Default is -1: if all elements to the left are taller,
// we can extend the current height all the way to the left, so the boundary is -1
//
// Note: We use -1 instead of 0 because we're storing indices
const lefts = new Array(n).fill(-1)

// Find the right boundary (smaller than current element) for each bar
// Default is n: if all elements to the right are taller,
// we can extend the current height all the way to the right, so the boundary is n
const rights = new Array(n).fill(n)

const stack = []

// Standard monotonic stack: find the right boundary (smaller element) for each bar
for (let i = 0; i < n; i++) {
while (stack.length > 0 && heights[stack[stack.length - 1]] > heights[i]) {
rights[stack.pop()] = i
}

stack.push(i)
}

// Standard monotonic stack: find the left boundary (smaller element) for each bar
for (let i = n - 1; i >= 0; i--) {
while (stack.length > 0 && heights[stack[stack.length - 1]] > heights[i]) {
lefts[stack.pop()] = i
}

stack.push(i)
}

let max = 0

// Calculate the maximum area using left/right boundaries and current bar height
for (let i = 0; i < n; i++) {
max = Math.max(max, (rights[i] - lefts[i] - 1) * heights[i])
}

return max
}
  • Time Complexity: O(n)
  • Space Complexity: O(n)