Maximize the rectangular area under Histogram

前端 未结 11 871
天涯浪人
天涯浪人 2020-11-28 17:26

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

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


        
11条回答
  •  执念已碎
    2020-11-28 18:03

    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 left = new Stack();
          Stack right = new Stack();
      
          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;
      }
      

提交回复
热议问题