Loading problems...
TL;DR: The optimal solution uses a monotonic increasing stack to find the nearest smaller bars on the left and right for every element in time, calculating the maximum possible area where each bar acts as the height bottleneck.
The LeetCode 84 problem asks us to find the area of the largest rectangle that can be formed within a histogram. We are given an array of integers representing the heights of bars, assuming each bar has a width of 1. We must determine the contiguous sub-array of bars that forms the largest rectangular area, where the height of the rectangle is limited by the shortest bar in that sub-array.
This is a classic interview problem that tests the ability to optimize geometric array problems using stack-based data structures.
The naive approach involves checking every possible pair of bars to define the start and end of a rectangle, or alternatively, expanding outwards from every bar to find its maximum width.
For each bar at index i, we can treat as the height of a potential rectangle. We then expand to the left and right as long as the adjacent bars are greater than or equal to . Once we hit a bar smaller than (or the array boundaries), we stop. The width is the distance between these boundaries.
heights[i]heights[i]heights[i]Pseudo-code:
textmax_area = 0 for i from 0 to n-1: height = heights[i] left = i right = i // Expand left while left >= 0 and heights[left] >= height: left-- // Expand right while right < n and heights[right] >= height: right++ width = (right - 1) - (left + 1) + 1 max_area = max(max_area, height * width) return max_area
Complexity Analysis:
The time complexity is . In the worst case (e.g., a sorted array like [1, 2, 3, 4, 5]), for every bar, the inner loops traverse a significant portion of the array. Given the constraint , an solution performs approximately operations, which results in a Time Limit Exceeded (TLE) error.
The inefficiency of the brute force method comes from repeatedly scanning the array to find how far a bar can extend. We need a way to determine the left and right boundaries for every bar in a single pass.
The core insight is that a bar at index i with height h can extend to the right until it encounters an index j where heights[j] < h. Similarly, it extends to the left until it hits an index k where heights[k] < h.
We can maintain a Monotonic Increasing Stack of indices. This stack maintains the invariant that the heights corresponding to the indices in the stack are always in strictly increasing order.
i is the first index to the right that is smaller.popped_index) to process its area, the new top of the stack (after the pop) represents the first index to the left that is smaller. This is the Left Boundary.By processing the area of a bar only when it is popped from the stack, we ensure that we know both its left limit (the element currently below it in the stack) and its right limit (the current element i that caused the pop).
Visual Description: Imagine iterating through the histogram. As long as bars are getting taller, we stack them up. The moment we see a drop in height (a smaller bar), we stop and look at the stack. The tall bars currently on the stack cannot extend past this new smaller bar. We pop the tallest bar. Its area is determined by its own height and the distance between the current "smaller" bar and the next bar remaining on the stack. We repeat this until the stack satisfies the increasing condition again.

Let's trace heights = [2, 1, 5, 6, 2, 3].
[0] (vals: [2]).1 < 2. Pop 0.
heights[0] = 2.1 - (-1) - 1 = 1. Area: 2 * 1 = 2. Max: 2.[1] (vals: [1]).5 > 1. Push 2. Stack: [1, 2] (vals: [1, 5]).6 > 5. Push 3. Stack: [1, 2, 3] (vals: [1, 5, 6]).2 < 6. Pop 3.
heights[3] = 6.4 - 2 - 1 = 1. Area: 6 * 1 = 6. Max: 6.[1, 2] (vals: [1, 5]).2 < 5. Pop 2.heights[2] = 5.4 - 1 - 1 = 2. Area: 5 * 2 = 10. Max: 10.[1] (vals: [1]).2 > 1. Push 4. Stack: [1, 4] (vals: [1, 2]).3 > 2. Push 5. Stack: [1, 4, 5] (vals: [1, 2, 3]).i = 6.
6 - 4 - 1 = 1. Area 3.6 - 1 - 1 = 4. Area 8.6 - (-1) - 1 = 6. Area 6.The algorithm ensures correctness by systematically considering every bar as the shortest bar in a potential rectangle. For any specific bar x, the widest possible rectangle with height heights[x] extends from the first element smaller than x on the left to the first element smaller than x on the right.
The monotonic stack invariant guarantees that when we pop an index, we have correctly identified these exact boundaries. Since the maximum area rectangle must have some bar that serves as its minimum height bottleneck, and we calculate the max area for every bar serving as that bottleneck, the global maximum area is guaranteed to be found.
cpp#include <vector> #include <stack> #include <algorithm> using namespace std; class Solution { public: int largestRectangleArea(vector<int>& heights) { stack<int> s; int maxArea = 0; int n = heights.size(); for (int i = 0; i <= n; i++) { // Use current height or 0 if we reached the end to flush stack int currentHeight = (i == n) ? 0 : heights[i]; // Maintain monotonic increasing stack while (!s.empty() && currentHeight < heights[s.top()]) { int h = heights[s.top()]; s.pop(); // Determine width // Right boundary is i // Left boundary is s.top() (exclusive) // If stack is empty, width extends from 0 to i int w = s.empty() ? i : i - s.top() - 1; maxArea = max(maxArea, h * w); } s.push(i); } return maxArea; } };
javaimport java.util.ArrayDeque; import java.util.Deque; class Solution { public int largestRectangleArea(int[] heights) { Deque<Integer> stack = new ArrayDeque<>(); int maxArea = 0; int n = heights.length; // Iterate through all bars plus one extra iteration (i=n) to clear stack for (int i = 0; i <= n; i++) { int currentHeight = (i == n) ? 0 : heights[i]; while (!stack.isEmpty() && currentHeight < heights[stack.peek()]) { int h = heights[stack.pop()]; // Calculate width // Right boundary is i // Left boundary is stack.peek() (exclusive) int w = stack.isEmpty() ? i : i - stack.peek() - 1; maxArea = Math.max(maxArea, h * w); } stack.push(i); } return maxArea; } }
pythonclass Solution: def largestRectangleArea(self, heights: list[int]) -> int: stack = [] # Stores indices max_area = 0 n = len(heights) # Iterate through array. # We go up to n to handle the cleanup of remaining items in stack. for i in range(n + 1): # Use 0 height for index n to force pops current_height = heights[i] if i < n else 0 while stack and current_height < heights[stack[-1]]: h = heights[stack.pop()] # Determine width # If stack is empty, width is the entire distance to i # Else, width is distance between current i and new top w = i if not stack else i - stack[-1] - 1 max_area = max(max_area, h * w) stack.append(i) return max_area
Time Complexity: Every index is pushed onto the stack exactly once and popped from the stack exactly once. The operations inside the loop (calculating area, updating max) are constant time. Thus, the total time is linear relative to the input size.
Space Complexity: In the worst case (a strictly increasing array), the stack will store all indices.
The logic used in LeetCode 84: Largest Rectangle in Histogram is directly applicable to:
Q: Why does the naive solution result in Time Limit Exceeded (TLE)? The naive solution is . With , this results in roughly operations, which exceeds the typical 1-second execution limit (approx operations) allowed by LeetCode.
Common Mistake 1: Incorrect Width Calculation
i - stack.top().i - stack.top() - 1 (exclusive boundaries).Common Mistake 2: Forgetting the "Empty Stack" Case
stack.top() always exists when calculating width.i.Common Mistake 3: Ignoring Remaining Elements
maxArea immediately after the for loop without processing items left in the stack.[1, 2, 3]), nothing is ever popped inside the loop.0 at index n to flush the stack.