Maximize the rectangular area under Histogram
Asked Answered
H

13

77

I have a histogram with integer heights and constant width 1. I want to maximize the rectangular area under a histogram. e.g.:

 _
| |
| |_ 
|   |
|   |_
|     |

The answer for this would be 6, 3 * 2, using col1 and col2.

O(n^2) brute force is clear to me, I would like an O(n log n) algorithm. I'm trying to think dynamic programming along the lines of maximum increasing subsequence O(n log n) algo, but am not going forward. Should I use divide and conquer algorithm?

PS: People with enough reputation are requested to remove the divide-and-conquer tag if there is no such solution.

After mho's comments: I mean the area of largest rectangle that fits entirely. (Thanks j_random_hacker for clarifying :) ).

Hodess answered 30/11, 2010 at 8:8 Comment(5)
How could area be 3*2! If the columns are of heights 3, 2, 1 respectively then area = 3 + 2 + 1 = 6. Also, maximizing against what? what is that you can change? The question is not clear yet.Tenderize
@mho: I believe by "maximize the rectangular area" he/she means "find the area of the largest rectangle that fits entirely under the histogram".Skylark
You have a lot of solutions here, both O(n log n) and O(n).Carroty
The official url is informatik.uni-ulm.de/acm/Locals/2003/html/judge.htmlChickadee
I found a graphically explained O(n) solution here Largest_Rectangle_In_HistogramObi
S
109

The above answers have given the best O(n) solution in code, however, their explanations are quite tough to comprehend. The O(n) algorithm using a stack seemed magic to me at first, but right now it makes every sense to me. OK, let me explain it.

First observation:

To find the maximal rectangle, if for every bar x, we know the first smaller bar on its each side, let's say l and r, we are certain that height[x] * (r - l - 1) is the best shot we can get by using height of bar x. In the figure below, 1 and 2 are the first smaller of 5.

OK, let's assume we can do this in O(1) time for each bar, then we can solve this problem in O(n)! by scanning each bar.

enter image description here

Then, the question comes: for every bar, can we really find the first smaller bar on its left and on its right in O(1) time? That seems impossible right? ... It is possible, by using a increasing stack.

Why using an increasing stack can keep track of the first smaller on its left and right?

Maybe by telling you that an increasing stack can do the job is not convincing at all, so I will walk you through this.

Firstly, to keep the stack increasing, we need one operation:

while x < stack.top():
    stack.pop()
stack.push(x)

Then you can check that in the increasing stack (as depicted below), for stack[x], stack[x-1] is the first smaller on its left, then a new element that can pop stack[x] out is the first smaller on its right.

enter image description here

Still can't believe stack[x-1] is the first smaller on the left on stack[x]?

I will prove it by contradiction.

First of all, stack[x-1] < stack[x] is for sure. But let's assume stack[x-1] is not the first smaller on the left of stack[x].

So where is the first smaller fs?

If fs < stack[x-1]:
    stack[x-1] will be popped out by fs,
else fs >= stack[x-1]:
    fs shall be pushed into stack,
Either case will result fs lie between stack[x-1] and stack[x], which is contradicting to the fact that there is no item between stack[x-1] and stack[x].

Therefore stack[x-1] must be the first smaller.

Summary:

Increasing stack can keep track of the first smaller on left and right for each element. By using this property, the maximal rectangle in histogram can be solved by using a stack in O(n).

Congratulations! This is really a tough problem, I'm glad my prosaic explanation didn't stop you from finishing. Attached is my proved solution as your reward :)

def largestRectangleArea(A):
    ans = 0
    A = [-1] + A
    A.append(-1)
    n = len(A)
    stack = [0]  # store index

    for i in range(n):
        while A[i] < A[stack[-1]]:
            h = A[stack.pop()]
            area = h*(i-stack[-1]-1)
            ans = max(ans, area)
        stack.append(i)
    return ans
Subjectivism answered 11/3, 2016 at 4:1 Comment(7)
I still don't get it, especially this statement "if for every bar x, we know the first smaller bar on its each side, let's say l and r, we are certain that height[x] * (r - l - 1) is the best shot we can get by using height of bar x". What if x is 5, but l and r are 3. Then max area is 3x3=9, and not 5*1=5 right?Hallerson
That doesn't contradict the quoted statement. What @Arrakëën is saying 5 is still the max area you can get by using (x, height[x]).Dermatoid
Whoever you are ! I just love you bro ! What an answer! Waited 3 months so that I can get commenting privilege just to comment here!Soni
"a new element that can pop stack[x] out is the first smaller on its right." Made the day for me.Rotation
I don't know why this problem is being asked in the interview. It's hard and non-sense because hardly someone can imagine a solution in that short time if they haven't seen the problem before.Size
I am a first year CS student. I would like to ask couple of questions about this solution. What is the purpose for A = [-1] + A. In python, I tried, but cannot add integer [-1] and list [2,1,5,6,2,3]. What is the purpose for appending -1 to the list? What is the purpose of initialising n = len(A). For [2,1,5,6,2,3, -1], n will be 7. I have two algorithm lectures so far. Since this solution use for loop and has while loop nested inside, can I say this is O(n2) time complexity algorithm?Gnosis
In this while loop, I assume it will start to iterate A=[2,1,5,6,2,3,-1] from left to right. while A[i] < A[stack[-1]]: I understand this part, but do not understand the logic part. I went through step by step in Python Tutor, but still do not understand the concept of this nested loop. Could you explain? Thanks!Gnosis
R
50

There are three ways to solve this problem in addition to the brute force approach. I will write down all of them. The java codes have passed tests in an online judge site called leetcode: http://www.leetcode.com/onlinejudge#question_84. so I am confident codes are correct.

Solution 1: dynamic programming + n*n matrix as cache

time: O(n^2), space: O(n^2)

Basic idea: use the n*n matrix dp[i][j] to cache the minimal height between bar[i] and bar[j]. Start filling the matrix from rectangles of width 1.

public int solution1(int[] height) {

    int n = height.length;
    if(n == 0) return 0;
    int[][] dp = new int[n][n];        
    int max = Integer.MIN_VALUE;

    for(int width = 1; width <= n; width++){

        for(int l = 0; l+width-1 < n; l++){

            int r = l + width - 1;

            if(width == 1){
                dp[l][l] = height[l];
                max = Math.max(max, dp[l][l]);
            } else {                    
                dp[l][r] = Math.min(dp[l][r-1], height[r]);
                max = Math.max(max, dp[l][r] * width);
            }                
        }
    }

    return max;
}

Solution 2: dynamic programming + 2 arrays as cache.

time: O(n^2), space: O(n)

Basic idea: this solution is like solution 1, but saves some space. The idea is that in solution 1 we build the matrix from row 1 to row n. But in each iteration, only the previous row contributes to the building of the current row. So we use two arrays as previous row and current row by turns.

public int Solution2(int[] height) {

    int n = height.length;
    if(n == 0) return 0;

    int max = Integer.MIN_VALUE;

    // dp[0] and dp[1] take turns to be the "previous" line.
    int[][] dp = new int[2][n];      

    for(int width = 1; width <= n; width++){

        for(int l = 0; l+width-1 < n; l++){

            if(width == 1){
                dp[width%2][l] = height[l];
            } else {
                dp[width%2][l] = Math.min(dp[1-width%2][l], height[l+width-1]);                     
            }
            max = Math.max(max, dp[width%2][l] * width);   
        }
    }        
    return max;
}

Solution 3: use stack.

time: O(n), space:O(n)

This solution is tricky and I learnt how to do this from explanation without graphs and explanation with graphs. I suggest you read the two links before reading my explanation below. It's hard to explain without graphs so my explanations might be hard to follow.

Following are my explanations:

  1. For each bar, we must be able to find the biggest rectangle containing this bar. So the biggest one of these n rectangles is what we want.

  2. To get the biggest rectangle for a certain bar (let's say bar[i], the (i+1)th bar), we just need to find out the biggest interval that contains this bar. What we know is that all the bars in this interval must be at least the same height with bar[i]. So if we figure out how many consecutive same-height-or-higher bars are there on the immediate left of bar[i], and how many consecutive same-height-or-higher bars are there on the immediate right of the bar[i], we will know the length of the interval, which is the width of the biggest rectangle for bar[i].

  3. To count the number of consecutive same-height-or-higher bars on the immediate left of bar[i], we only need to find the closest bar on the left that is shorter than the bar[i], because all the bars between this bar and bar[i] will be consecutive same-height-or-higher bars.

  4. We use a stack to dynamicly keep track of all the left bars that are shorter than a certain bar. In other words, if we iterate from the first bar to bar[i], when we just arrive at the bar[i] and haven't updated the stack, the stack should store all the bars that are no higher than bar[i-1], including bar[i-1] itself. We compare bar[i]'s height with every bar in the stack until we find one that is shorter than bar[i], which is the cloest shorter bar. If the bar[i] is higher than all the bars in the stack, it means all bars on the left of bar[i] are higher than bar[i].

  5. We can do the same thing on the right side of the i-th bar. Then we know for bar[i] how many bars are there in the interval.

    public int solution3(int[] height) {
    
        int n = height.length;
        if(n == 0) return 0;
    
        Stack<Integer> left = new Stack<Integer>();
        Stack<Integer> right = new Stack<Integer>();
    
        int[] width = new int[n];// widths of intervals.
        Arrays.fill(width, 1);// all intervals should at least be 1 unit wide.
    
        for(int i = 0; i < n; i++){
            // count # of consecutive higher bars on the left of the (i+1)th bar
            while(!left.isEmpty() && height[i] <= height[left.peek()]){
                // while there are bars stored in the stack, we check the bar on the top of the stack.
                left.pop();                
            }
    
            if(left.isEmpty()){
                // all elements on the left are larger than height[i].
                width[i] += i;
            } else {
                // bar[left.peek()] is the closest shorter bar.
                width[i] += i - left.peek() - 1;
            }
            left.push(i);
        }
    
        for (int i = n-1; i >=0; i--) {
    
            while(!right.isEmpty() && height[i] <= height[right.peek()]){                
                right.pop();                
            }
    
            if(right.isEmpty()){
                // all elements to the right are larger than height[i]
                width[i] += n - 1 - i;
            } else {
                width[i] += right.peek() - i - 1;
            }
            right.push(i);
        }
    
        int max = Integer.MIN_VALUE;
        for(int i = 0; i < n; i++){
            // find the maximum value of all rectangle areas.
            max = Math.max(max, width[i] * height[i]);
        }
    
        return max;
    }
    
Renettarenew answered 1/12, 2012 at 7:19 Comment(3)
I suggest you include this picture from a blog post (Chinese) to help illustrate the stack method. It's quite self-explaining.Longitude
While finding maximum of all rectangles, it should be max = Math.max(max,(width[i]+1)*height[i]) since we also have to include width of bar we are including.Mcgovern
@shivammitra The width of the bar is already included because all elements of the width array are initialized to 1 by the statement Arrays.fill(width,1).Sruti
G
15

Implementation in Python of the @IVlad's answer O(n) solution:

from collections import namedtuple

Info = namedtuple('Info', 'start height')

def max_rectangle_area(histogram):
    """Find the area of the largest rectangle that fits entirely under
    the histogram.

    """
    stack = []
    top = lambda: stack[-1]
    max_area = 0
    pos = 0 # current position in the histogram
    for pos, height in enumerate(histogram):
        start = pos # position where rectangle starts
        while True:
            if not stack or height > top().height:
                stack.append(Info(start, height)) # push
            elif stack and height < top().height:
                max_area = max(max_area, top().height*(pos-top().start))
                start, _ = stack.pop()
                continue
            break # height == top().height goes here

    pos += 1
    for start, height in stack:
        max_area = max(max_area, height*(pos-start))

    return max_area

Example:

>>> f = max_rectangle_area
>>> f([5,3,1])
6
>>> f([1,3,5])
6
>>> f([3,1,5])
5
>>> f([4,8,3,2,0])
9
>>> f([4,8,3,1,1,0])
9

Linear search using a stack of incomplete subproblems

Copy-paste algorithm's description (in case the page goes down):

We process the elements in left-to-right order and maintain a stack of information about started but yet unfinished subhistograms. Whenever a new element arrives it is subjected to the following rules. If the stack is empty we open a new subproblem by pushing the element onto the stack. Otherwise we compare it to the element on top of the stack. If the new one is greater we again push it. If the new one is equal we skip it. In all these cases, we continue with the next new element. If the new one is less, we finish the topmost subproblem by updating the maximum area w.r.t. the element at the top of the stack. Then, we discard the element at the top, and repeat the procedure keeping the current new element. This way, all subproblems are finished until the stack becomes empty, or its top element is less than or equal to the new element, leading to the actions described above. If all elements have been processed, and the stack is not yet empty, we finish the remaining subproblems by updating the maximum area w.r.t. to the elements at the top.

For the update w.r.t. an element, we find the largest rectangle that includes that element. Observe that an update of the maximum area is carried out for all elements except for those skipped. If an element is skipped, however, it has the same largest rectangle as the element on top of the stack at that time that will be updated later. The height of the largest rectangle is, of course, the value of the element. At the time of the update, we know how far the largest rectangle extends to the right of the element, because then, for the first time, a new element with smaller height arrived. The information, how far the largest rectangle extends to the left of the element, is available if we store it on the stack, too.

We therefore revise the procedure described above. If a new element is pushed immediately, either because the stack is empty or it is greater than the top element of the stack, the largest rectangle containing it extends to the left no farther than the current element. If it is pushed after several elements have been popped off the stack, because it is less than these elements, the largest rectangle containing it extends to the left as far as that of the most recently popped element.

Every element is pushed and popped at most once and in every step of the procedure at least one element is pushed or popped. Since the amount of work for the decisions and the update is constant, the complexity of the algorithm is O(n) by amortized analysis.

Goldplate answered 14/1, 2011 at 11:57 Comment(2)
I found this and other explanations hard to understand because I don't know what you-all mean by "subhistogram" -- starting and ending where? There are other issues, but that't the big one.Kerriekerrigan
@Aaron Watters: the start position of a subhistogram is stored explicitly on the stack (start attribute). The end position is the current position (pos variable) in the histogram when the stack pops, see height < top().height branch.Goldplate
S
15

The other answers here have done a great job presenting the O(n)-time, O(n)-space solution using two stacks. There's another perspective on this problem that independently provides an O(n)-time, O(n)-space solution to the problem, and might provide a little bit more insight as to why the stack-based solution works.

The key idea is to use a data structure called a Cartesian tree. A Cartesian tree is a binary tree structure (though not a binary search tree) that's built around an input array. Specifically, the root of the Cartesian tree is built above the minimum element of the array, and the left and right subtrees are recursively constructed from the subarrays to the left and right of the minimum value.

For example, here's a sample array and its Cartesian tree:

                 +----------------------- 23 ------+
                 |                                 |
  +------------- 26 --+                        +-- 79
  |                   |                        |
  31 --+              53 --+                   84
       |                   |
       41 --+              58 -------+
            |                        |
            59                  +-- 93
                                |
                                97

+----+----+----+----+----+----+----+----+----+----+----+
| 31 | 41 | 59 | 26 | 53 | 58 | 97 | 93 | 23 | 84 | 79 |
+----+----+----+----+----+----+----+----+----+----+----+

The reason that Cartesian trees are useful in this problem is that the question at hand has a really nice recursive structure to it. Begin by looking at the lowest rectangle in the histogram. There are three options for where the maximum rectangle could end up being placed:

  • It could pass right under the minimum value in the histogram. In that case, to make it as large as possible, we'd want to make it as wide as the entire array.

  • It could be entirely to the left of the minimum value. In that case, we recursively want the answer formed from the subarray purely to the left of the minimum value.

  • It could be entirely to the right of the minimum value. In that case, we recursively want the answer formed from the subarray purely to the right of the minimum value.

Notice that this recursive structure - find the minimum value, do something with the subarrays to the left and the right of that value - perfectly matches the recursive structure of a Cartesian tree. In fact, if we can create a Cartesian tree for the overall array when we get started, we can then solve this problem by recursively walking the Cartesian tree from the root downward. At each point, we recursively compute the optimal rectangle in the left and right subarrays, along with the rectangle you'd get by fitting right under the minimum value, and then return the best option we find.

In pseudocode, this looks like this:

function largestRectangleUnder(int low, int high, Node root) {
  /* Base case: If the range is empty, the biggest rectangle we
   * can fit is the empty rectangle.
   */
  if (low == high) return 0;

  /* Assume the Cartesian tree nodes are annotated with their
   * positions in the original array.
   */
  return max {
    (high - low) * root.value, // Widest rectangle under the minimum
    largestRectangleUnder(low,            root.index, root.left),
    largestRectnagleUnder(root.index + 1, high,       root.right)
  }
}

Once we have the Cartesian tree, this algorithm takes time O(n), since we visit each node exactly once and do O(1) work per node.

It turns out that there's a simple, linear-time algorithm for building Cartesian trees. The "natural" way you'd probably think to build one would be to scan across the array, find the minimum value, then recursively build a Cartesian tree from the left and right subarrays. The problem is that the process of finding the minimum value is really expensive, and this can take time Θ(n2).

The "fast" way to build a Cartesian tree is by scanning the array from the left to the right, adding in one element at a time. This algorithm is based on the following observations about Cartesian trees:

  • First, Cartesian trees obey the heap property: every element is less than or equal to its children. The reason for this is that the Cartesian tree root is the smallest value in the overall array, and its children are the smallest elements in their subarrays, etc.

  • Second, if you do an inorder traversal of a Cartesian tree, you get back the elements of the array in the order in which they appear. To see why this is, notice that if you do an inorder traversal of a Cartesian tree, you first visit everything to the left of the minimum value, then the minimum value, then everything to the right of the minimum value. Those visitations are recursively done the same way, so everything ends up being visited in order.

These two rules give us a lot of information about what happens if we start with a Cartesian tree of the first k elements of the array and want to form a Cartesian tree for the first k+1 elements. That new element will have to end up on the right spine of the Cartesian tree - the part of the tree formed by starting at the root and only taking steps to the right - because otherwise something would come after it in an inorder traversal. And, within that right spine, it has to be placed in a way that makes it bigger than everything above it, since we need to obey the heap property.

The way that you actually add a new node to the Cartesian tree is to start at the rightmost node in the tree and walk upwards until you either hit the root of the tree or find a node that has a smaller value. You then make the new value have as its left child the last node it walked up on top of.

Here's a trace of that algorithm on a small array:

+---+---+---+---+
| 2 | 4 | 3 | 1 |
+---+---+---+---+

2 becomes the root.

  2 --+
      |  
      4

4 is bigger than 2, we can't move upwards. Append to right.

+---+---+---+---+
| 2 | 4 | 3 | 1 |
+---+---+---+---+

  2 ------+
          |
      --- 3 
      |
      4

3 is lesser than 4, climb over it. Can't climb further over 2, as it is smaller than 3. Climbed over subtree rooted at 4 goes to the left of new value 3 and 3 becomes rightmost node now.

+---+---+---+---+
| 2 | 4 | 3 | 1 |
+---+---+---+---+

  +---------- 1
  |
  2 ------+
          |
      --- 3
      |
      4

1 climbs over the root 2, the entire tree rooted at 2 is moved to left of 1, and 1 is now the new root - and also the rightmost value.

+---+---+---+---+
| 2 | 4 | 3 | 1 |
+---+---+---+---+

Although this might not seem to run in linear time - wouldn't you potentially end up climbing all the way to the root of the tree over and over and over again? - you can show that this runs in linear time using a clever argument. If you climb up over a node in the right spine during an insertion, that node ends up getting moved off the right spine and therefore can't be rescanned in a future insertion. Therefore, every node is only ever scanned over at most once, so the total work done is linear.

And now the kicker - the standard way that you'd actually implement this approach is by maintaining a stack of the values that correspond to the nodes on the right spine. The act of "walking up" and over a node corresponds to popping a node off the stack. Therefore, the code for building a Cartesian tree looks something like this:

Stack s;
for (each array element x) {
   pop s until it's empty or s.top > x
   push x onto the stack.
   do some sort of pointer rewiring based on what you just did.
}

The stack manipulations here might seem really familiar, and that's because these are the exact stack operations that you would do in the answers shown elsewhere here. In fact, you can think of what those approaches are doing as implicitly building the Cartesian tree and running the recursive algorithm shown above in the process of doing so.

The advantage, I think, of knowing about Cartesian trees is that it provides a really nice conceptual framework for seeing why this algorithm works correctly. If you know that what you're doing is running a recursive walk of a Cartesian tree, it's easier to see that you're guaranteed to find the largest rectangle. Plus, knowing that the Cartesian tree exists gives you a useful tool for solving other problems. Cartesian trees show up in the design of fast data structures for the range minimum query problem and are used to convert suffix arrays into suffix trees.


Here's some Java code that implements this idea, courtesy of @Azeem!

import java.util.Stack;

public class CartesianTreeMakerUtil {

    private static class Node {
        int val;
        Node left;
        Node right;
    }

    public static Node cartesianTreeFor(int[] nums) {
        Node root = null;
        Stack<Node> s = new Stack<>();
        for(int curr : nums) {
            Node lastJumpedOver = null;
            while(!s.empty() && s.peek().val > curr) {
                lastJumpedOver = s.pop();
            }
            Node currNode = this.new Node();
            currNode.val = curr;
            if(s.isEmpty()) {
                root = currNode;
            }
            else {
                s.peek().right = currNode;
            }
            currNode.left = lastJumpedOver;
            s.push(currNode);
        }
        return root;
    }
    
    public static void printInOrder(Node root) {
        if(root == null) return;
        if(root.left != null ) {
            printInOrder(root.left);
        }
        System.out.println(root.val);
        if(root.right != null) {
            printInOrder(root.right);
        }
    }
    
    public static void main(String[] args) {
        int[] nums = new int[args.length];
        for (int i = 0; i < args.length; i++) {
            nums[i] = Integer.parseInt(args[i]);
        }
        Node root = cartesianTreeFor(nums);
        tester.printInOrder(root);
    }
}
Shorts answered 1/6, 2018 at 22:19 Comment(0)
S
5

The easiest solution in O(N)

long long getMaxArea(long long hist[], long long n)
{

    stack<long long> s;

    long long max_area = 0; 
    long long tp;  
    long long area_with_top; 

    long long i = 0;
    while (i < n)
    {
        if (s.empty() || hist[s.top()] <= hist[i])
            s.push(i++);
       else
        {
            tp = s.top();  // store the top index
            s.pop();  // pop the top
            area_with_top = hist[tp] * (s.empty() ? i : i - s.top() - 1);
            if (max_area < area_with_top)
            {
                max_area = area_with_top;
            }
        }
    }

   while (!s.empty())
    {
        tp = s.top();
        s.pop();
        area_with_top = hist[tp] * (s.empty() ? i : i - s.top() - 1);

        if (max_area < area_with_top)
            max_area = area_with_top;
    }

    return max_area;
}
Specify answered 5/10, 2014 at 12:49 Comment(0)
U
2

There is also another solution using Divide and Conquer. The algorithm for it is :

1) Divide the array into 2 parts with the smallest height as the breaking point

2) The maximum area is the maximum of : a) Smallest height * size of the array b) Maximum rectangle in left half array c) Maximum rectangle in right half array

The time complexity comes to O(nlogn)

Unger answered 8/7, 2016 at 1:26 Comment(1)
This is good solution. Although the worst case would be O(n2).Aftersensation
L
2

The stack solution is one of the most clever solutions I've seen till date. And it can be a little hard to understand why that works.

I've taken a jab at explaining the same in some detail here.

Summary points from the post:-

  • General way our brain thinks is :-
    • Create every situation and try to find the value of the contraint that is needed to solve the problem.
    • And we happily convert that to code as :- find the value of contraint(min) for each situation(pair(i,j))

The clever solutions tries to flip the problem.For each constraint/min value of tha area, what is the best possible left and right extremes ?

  • So if we traverse over each possible min in the array. What are the left and right extremes for each value ?

    • Little thought says, the first left most value less than the current min and similarly the first rightmost value that is lesser than the current min.
  • So now we need to see if we can find a clever way to find the first left and right values lesser than the current value.

  • To think: If we have traversed the array partially say till min_i, how can the solution to min_i+1 be built?

  • We need the first value less than min_i to its left.

  • Inverting the statement : we need to ignore all values to the left of min_i that are greater than min_i. We stop when we find the first value smaller than min_i (i) . The troughs in the curve hence become useless once we have crossed it. In histogram , (2 4 3) => if 3 is min_i, 4 being larger is not of interest.
  • Corrollary: in a range (i,j). j being the min value we are considering.. all values between j and its left value i are useless. Even for further calculations.
  • Any histogram on the right with a min value larger than j, will be binded at j. The values of interest on the left form a monotonically increasing sequence with j being the largest value. (Values of interest here being possible values that may be of interest for the later array)
  • Since, we are travelling from left to right, for each min value/ current value - we do not know whether the right side of the array will have an element smaller than it.
    • So we have to keep it in memory until we get to know this value is useless. (since a smaller value is found)
  • All this leads to a usage of our very own stack structure.

    • We keep on stack until we don't know its useless.
    • We remove from stack once we know the thing is crap.
  • So for each min value to find its left smaller value, we do the following:-

    1. pop the elements larger to it (useless values)
    2. The first element smaller than the value is the left extreme. The i to our min.
  • We can do the same thing from the right side of the array and we will get j to our min.

It's quite hard to explain this, but if this is making sense then I'd suggest read the complete article here since it has more insights and details.

Linn answered 30/1, 2017 at 20:35 Comment(0)
K
1

I don't understand the other entries, but I think I know how to do it in O(n) as follows.

A) for each index find the largest rectangle inside the histogram ending at that index where the index column touches the top of the rectangle and remember where the rectangle starts. This can be done in O(n) using a stack based algorithm.

B) Similarly for each index find the largest rectangle starting at that index where the index column touches the top of the rectangle and remember where the rectangle ends. Also O(n) using the same method as (A) but scanning the histogram backwards.

C) For each index combine the results of (A) and (B) to determine the largest rectangle where the column at that index touches the top of the rectangle. O(n) like (A).

D) Since the largest rectangle must be touched by some column of the histogram the largest rectangle is the largest rectangle found in step (C).

The hard part is implementing (A) and (B), which I think is what JF Sebastian may have solved rather than the general problem stated.

Kerriekerrigan answered 2/6, 2011 at 19:48 Comment(1)
BTW, here is the site I got the basic idea from: tech-queries.blogspot.com/2011/03/…Kerriekerrigan
B
1

I coded this one and felt little better in the sense:

import java.util.Stack;

     class StackItem{
       public int sup;
       public int height;
       public int sub;

       public StackItem(int a, int b, int c){
           sup = a;
           height = b;
           sub =c;
       }
       public int getArea(){
           return (sup - sub)* height;
       }


       @Override
       public String toString(){
       return "     from:"+sup+
              "     to:"+sub+
              "     height:"+height+              
              "     Area ="+getArea();
       }
    }   


public class MaxRectangleInHistogram {    
    Stack<StackItem> S;
    StackItem curr;
    StackItem maxRectangle;

    public StackItem getMaxRectangleInHistogram(int A[], int n){
        int i = 0;
        S = new Stack();        
        S.push(new StackItem(0,0,-1));
        maxRectangle = new StackItem(0,0,-1);

        while(i<n){

                curr = new StackItem(i,A[i],i);

                    if(curr.height > S.peek().height){
                            S.push(curr); 
                    }else if(curr.height == S.peek().height){                            
                            S.peek().sup = i+1;                         
                    }else if(curr.height < S.peek().height){                            

                            while((S.size()>1) && (curr.height<=S.peek().height)){
                                curr.sub = S.peek().sub;
                                S.peek().sup = i;
                                decideMaxRectangle(S.peek());
                                S.pop(); 
                            }                               
                        S.push(curr);                    
                    }
            i++;
        }

        while(S.size()>1){ 
            S.peek().sup = i;
            decideMaxRectangle(S.peek());
            S.pop();            
        }  

        return maxRectangle;
    }

    private void decideMaxRectangle(StackItem s){ 

        if(s.getArea() > maxRectangle.getArea() )
            maxRectangle = s;      
    }

}

Just Note:

Time Complexity: T(n) < O(2n) ~ O(n)
Space Complexity S(n) < O(n)
Broadus answered 12/11, 2012 at 11:24 Comment(0)
A
1

I would like to thank @templatetypedef for his/her extremely detailed and intuitive answer. The Java code below is based on his suggestion to use Cartesian Trees and solves the problem in O(N) time and O(N) space. I suggest that you read @templatetypedef's answer above before reading the code below. The code is given in the format of the solution to the problem at leetcode: https://leetcode.com/problems/largest-rectangle-in-histogram/description/ and passes all 96 test cases.

class Solution {

private class Node {
    int val;
    Node left;
    Node right;
    int index;
}

public  Node getCartesianTreeFromArray(int [] nums) {
    Node root = null;
    Stack<Node> s = new Stack<>();
    for(int i = 0; i < nums.length; i++) {
        int curr = nums[i];
        Node lastJumpedOver = null;
        while(!s.empty() && s.peek().val >= curr) {
            lastJumpedOver = s.pop();
        }
        Node currNode = this.new Node();
        currNode.val = curr;
        currNode.index = i;
        if(s.isEmpty()) {
            root = currNode;
        }
        else {
            s.peek().right = currNode;
        }
        currNode.left = lastJumpedOver;
        s.push(currNode);
    }
    return root;
}

public int largestRectangleUnder(int low, int high, Node root, int [] nums) {
    /* Base case: If the range is empty, the biggest rectangle we
     * can fit is the empty rectangle.
     */
    if(root == null) return 0;

    if (low == high) {
        if(0 <= low && low <= nums.length - 1) {
            return nums[low];
        }
        return 0;
    }

    /* Assume the Cartesian tree nodes are annotated with their
     * positions in the original array.
     */
    int leftArea = -1 , rightArea= -1;
    if(root.left != null) {
        leftArea = largestRectangleUnder(low, root.index - 1 , root.left, nums);
    }
    if(root.right != null) {
        rightArea = largestRectangleUnder(root.index + 1, high,root.right, nums);
    }
    return Math.max((high - low  + 1) * root.val, 
           Math.max(leftArea, rightArea));
}

public int largestRectangleArea(int[] heights) {
    if(heights == null || heights.length == 0 ) {
        return 0;
    }
    if(heights.length == 1) {
        return heights[0];
    }
    Node root = getCartesianTreeFromArray(heights);
    return largestRectangleUnder(0, heights.length - 1, root, heights);
}

}

Arne answered 4/6, 2018 at 19:2 Comment(2)
I ended up incorporating your code into my answer after a few quick edits. Thanks for writing this!Shorts
Your explanation of Cartesian trees motivated me to code up a working solution - I am glad to have been of help. If you'd like to, you can incorporate my Java code for the largestRectangleUnder() function pseudocode in your answer. It handles some edge cases that the pseudocode is missing.Arne
K
0

    a=[3,4,7,4,6]
    a.sort()
    r=0
    for i in range(len(a)):
        if a[i]* (n-1) > r:
            r = a[i]*(n-i)
    print(r)

output:

16

Kbp answered 8/10, 2021 at 10:47 Comment(0)
G
0

I come across this question in one of interview. Was trying to solve this, resulting in observed following things -

  1. Need to check consecutive left elements greater than current element
  2. Need to check consecutive right elements greater than current element
  3. Calculate area (number of left side max elements + number of right side max elements + 1) * current element
  4. Check and replace existing maxArea if calculated area is greater than maxArea

Following is the JS code implementing above pseudocode

function maxAreaCovered(arr) {
    let maxArea = 0;
    for (let index = 0; index < arr.length; index++) {
        let l = index - 1;
        let r = index + 1;
        let maxEleCount = 0
        while (l > -1) {
            if (arr[l] >= arr[index]) {
                maxEleCount++;
            } else {
                break;
            }
            l--;
        }
        while (r < arr.length) {
            if (arr[r] >= arr[index]) {
                maxEleCount++;
            } else {
                break;
            }
            r++;
        }
        let area = (maxEleCount + 1) * arr[index];
        maxArea = Math.max(area, maxArea);
    }
    return maxArea
}

console.log(maxAreaCovered([6, 2, 5, 4, 5, 1, 6]));
Gerome answered 5/10, 2022 at 17:6 Comment(0)
I
-1

You can use O(n) method which uses stack to calculate the maximum area under the histogram.

long long histogramArea(vector<int> &histo){
   stack<int> s;
   long long maxArea=0;
   long long area= 0;
   int i =0;
   for (i = 0; i < histo.size();) {
    if(s.empty() || histo[s.top()] <= histo[i]){
        s.push(i++);
    }
    else{
        int top = s.top(); s.pop();
        area= histo[top]* (s.empty()?i:i-s.top()-1);
        if(area >maxArea)
            maxArea= area;
    }
  }
  while(!s.empty()){
    int top = s.top();s.pop();
    area= histo[top]* (s.empty()?i:i-s.top()-1);
    if(area >maxArea)
        maxArea= area;
 }
 return maxArea;
}

For explanation you can read here http://www.geeksforgeeks.org/largest-rectangle-under-histogram/

Interpretive answered 3/10, 2015 at 18:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.