84. Largest Rectangle in Histogram

Efficiently Solving the Largest Rectangle in Histogram Problem

In this post, we’ll explore an efficient solution to one of the classic problems in data structures—finding the largest rectangle in a histogram. This problem is particularly popular in coding interviews and is a great example of using stacks to solve complex problems in linear time.

Problem Statement

Given an array of integers where each element represents the height of a bar in a histogram, the goal is to find the area of the largest rectangle that can be formed using contiguous bars. The width of each bar is 1 unit.

For example, in the histogram represented by [2,1,5,6,2,3], the largest rectangle has an area of 10.

Approach

To solve this problem efficiently, we can use a stack-based approach. The idea is to preprocess two pieces of information for each bar:

  1. Next Smaller Element: The index of the first bar to the right that has a smaller height.
  2. Previous Smaller Element: The index of the first bar to the left that has a smaller height.

Once we have this information, we can easily compute the maximum rectangle area for each bar.

Solution Breakdown

Here is the Java code to implement this solution:

java 

class Solution {
    public int largestRectangleArea(int[] heights) {
        int size = heights.length;
        Stack<Integer> stack = new Stack();
        int[] nextSmall = new int[size];
        //calculating next small elements;
        stack.push(size - 1);
        nextSmall[size - 1] = size;
        for(int i = size - 2; i >= 0; i--) {
            while(stack.size() > 0 && heights[stack.peek()] >= heights[i]) {
                stack.pop();
            }
            if(stack.size() == 0) {
                nextSmall[i] = size;
            }else {
                nextSmall[i] = stack.peek();
            }
            stack.push(i);
        }
        //claer the stack
        stack.clear();
        //calculate the previous small elements
        int[] prevSmall = new int[size];
        stack.push(0);
        prevSmall[0] = -1;
        for(int i = 1; i < size; i++) {
            while(stack.size() > 0 && heights[stack.peek()] >= heights[i]) {
                stack.pop();
            }
            if(stack.size() == 0){
                prevSmall[i] = -1;
            }else {
                prevSmall[i] = stack.peek();
            }
            stack.push(i);
        }
        //claculating the area
        int max = -1;
        for(int i = 0; i < size; i++) {
            int area = heights[i] * (nextSmall[i] - prevSmall[i] - 1);
            max = Math.max(area, max);
        }
        return max;
    }
}

Step-by-Step Explanation

Let’s break down the logic behind the code:

  1. Next Smaller Element Calculation:

    • We iterate through the heights array in reverse order.
    • For each bar at index i, we check the top of the stack to find the next smaller bar. If the current bar is taller than the bar at the top of the stack, we pop the stack. Otherwise, we record the index of the next smaller bar.
    • If there is no smaller bar to the right, we set the index to size (i.e., beyond the last bar).
  2. Previous Smaller Element Calculation:

    • Now we clear the stack and process the array from left to right.
    • Similar to the previous step, for each bar, we find the first smaller bar on the left and record its index.
    • If no smaller bar exists, we set the index to -1.
  3. Calculating the Maximum Area:

    • For each bar at index i, the width of the rectangle it can form is calculated as:
      Width = nextSmall[i] - prevSmall[i] - 1
    • The area of the rectangle is simply the height of the bar multiplied by the width:
      Area = heights[i] * Width
    • We track the maximum area across all bars and return it as the result.

Time Complexity

  • Next Smaller Element: Each element is pushed and popped from the stack at most once, so this part takes O(n) time.
  • Previous Smaller Element: Similarly, this part also takes O(n) time.
  • Calculating the Area: This step is a simple linear pass through the array, which takes O(n) time.

Thus, the overall time complexity is O(n), making this solution highly efficient compared to a brute-force approach which would have taken O(n²) time.

Key Insights

  1. Stacks: The use of stacks helps efficiently compute the next and previous smaller elements.
  2. Optimality: By reducing the problem to linear time, we significantly improve performance for large histograms.
  3. Edge Cases: The code handles cases where there are no smaller elements on either side, ensuring the entire array is considered when needed.

Conclusion

The problem of finding the largest rectangle in a histogram is a great example of how a stack can be used to solve a seemingly complex problem in linear time. By breaking the problem into smaller subproblems (finding the next and previous smaller elements), we reduce the complexity and arrive at an optimal solution.

This approach is not only efficient but also highlights the importance of understanding data structures, particularly stacks, in solving real-world algorithmic problems.

Comments

Popular posts from this blog

3Sum Closest: O(n²) Two-Pointer Magic 🚀 leetcode: 16

Kadane's Algorithm: Maximum Subarray Problem - LeetCode(53) Solution

LeetCode Problem: Implement pow(x, n) leetcode:- 50