Open In App

Largest Rectangular Area in a Histogram using Segment Tree

Improve
Improve
Like Article
Like
Save
Share
Report

Find the largest rectangular area possible in a given histogram where the largest rectangle can be made of a number of contiguous bars. For simplicity, assume that all bars have same width and the width is 1 unit. 

For example, consider the following histogram with 7 bars of heights {6, 2, 5, 4, 5, 1, 6}. The largest possible rectangle possible is 12 (see the below figure, the max area rectangle is highlighted in red)
 

histogram

A simple solution is to one by one consider all bars as starting points and calculate area of all rectangles starting with every bar. Finally return maximum of all possible areas. Time complexity of this solution would be O(n^2).

We can use Divide and Conquer to solve this in O(nLogn) time. The idea is to find the minimum value in the given array. Once we have index of the minimum value, the max area is maximum of following three values. 

  1. Maximum area in left side of minimum value (Not including the min value) 
  2. Maximum area in right side of minimum value (Not including the min value) 
  3. Number of bars multiplied by minimum value. 

The areas in left and right of minimum value bar can be calculated recursively. If we use linear search to find the minimum value, then the worst case time complexity of this algorithm becomes O(n^2). In worst case, we always have (n-1) elements in one side and 0 elements in other side and if the finding minimum takes O(n) time, we get the recurrence similar to worst case of Quick Sort. 
How to find the minimum efficiently? Range Minimum Query using Segment Tree can be used for this. We build segment tree of the given histogram heights. Once the segment tree is built, all range minimum queries take O(Logn) time. So over all complexity of the algorithm becomes.

Overall Time = Time to build Segment Tree + Time to recursively find maximum area Time to build segment tree is O(n). Let the time to recursively find max area be T(n). It can be written as following.  T(n) = O(Logn) + T(n-1)  The solution of above recurrence is O(nLogn). So overall time is O(n) + O(nLogn) which is O(nLogn). Following is C++ implementation of the above algorithm. 

Implementation:

C++




// A Divide and Conquer Program to find maximum rectangular area in a histogram
#include <bits/stdc++.h>
using namespace std;
 
// A utility function to find minimum of three integers
int max(int x, int y, int z)
return max(max(x, y), z); }
 
// A utility function to get minimum of two numbers in hist[]
int minVal(int *hist, int i, int j)
{
    if (i == -1) return j;
    if (j == -1) return i;
    return (hist[i] < hist[j])? i : j;
}
 
// A utility function to get the middle index from corner indexes.
int getMid(int s, int e)
{   return s + (e -s)/2; }
 
/*  A recursive function to get the index of minimum value in a given range of
    indexes. The following are parameters for this function.
 
    hist   --> Input array for which segment tree is built
    st    --> Pointer to segment tree
    index --> Index of current node in the segment tree. Initially 0 is
             passed as root is always at index 0
    ss & se  --> Starting and ending indexes of the segment represented by
                 current node, i.e., st[index]
    qs & qe  --> Starting and ending indexes of query range */
int RMQUtil(int *hist, int *st, int ss, int se, int qs, int qe, int index)
{
    // If segment of this node is a part of given range, then return the
    // min of the segment
    if (qs <= ss && qe >= se)
        return st[index];
 
    // If segment of this node is outside the given range
    if (se < qs || ss > qe)
        return -1;
 
    // If a part of this segment overlaps with the given range
    int mid = getMid(ss, se);
    return minVal(hist, RMQUtil(hist, st, ss, mid, qs, qe, 2*index+1),
                  RMQUtil(hist, st, mid+1, se, qs, qe, 2*index+2));
}
 
// Return index of minimum element in range from index qs (query start) to
// qe (query end).  It mainly uses RMQUtil()
int RMQ(int *hist, int *st, int n, int qs, int qe)
{
    // Check for erroneous input values
    if (qs < 0 || qe > n-1 || qs > qe)
    {
        cout << "Invalid Input";
        return -1;
    }
 
    return RMQUtil(hist, st, 0, n-1, qs, qe, 0);
}
 
// A recursive function that constructs Segment Tree for hist[ss..se].
// si is index of current node in segment tree st
int constructSTUtil(int hist[], int ss, int se, int *st, int si)
{
    // If there is one element in array, store it in current node of
    // segment tree and return
    if (ss == se)
       return (st[si] = ss);
 
    // If there are more than one elements, then recur for left and
    // right subtrees and store the minimum of two values in this node
    int mid = getMid(ss, se);
    st[si] =  minVal(hist, constructSTUtil(hist, ss, mid, st, si*2+1),
                     constructSTUtil(hist, mid+1, se, st, si*2+2));
    return st[si];
}
 
/* Function to construct segment tree from given array. This function
   allocates memory for segment tree and calls constructSTUtil() to
   fill the allocated memory */
int *constructST(int hist[], int n)
{
    // Allocate memory for segment tree
    int x = (int)(ceil(log2(n))); //Height of segment tree
    int max_size = 2*(int)pow(2, x) - 1; //Maximum size of segment tree
    int *st = new int[max_size];
 
    // Fill the allocated memory st
    constructSTUtil(hist, 0, n-1, st, 0);
 
    // Return the constructed segment tree
    return st;
}
 
// A recursive function to find the maximum rectangular area.
// It uses segment tree 'st' to find the minimum value in hist[l..r]
int getMaxAreaRec(int *hist, int *st, int n, int l, int r)
{
    // Base cases
    if (l > r)  return INT_MIN;
    if (l == r)  return hist[l];
 
    // Find index of the minimum value in given range
    // This takes O(Logn)time
    int m = RMQ(hist, st, n, l, r);
 
    /* Return maximum of following three possible cases
       a) Maximum area in Left of min value (not including the min)
       a) Maximum area in right of min value (not including the min)
       c) Maximum area including min */
    return max(getMaxAreaRec(hist, st, n, l, m-1),
               getMaxAreaRec(hist, st, n, m+1, r),
               (r-l+1)*(hist[m]) );
}
 
// The main function to find max area
int getMaxArea(int hist[], int n)
{
    // Build segment tree from given array. This takes
    // O(n) time
    int *st = constructST(hist, n);
 
    // Use recursive utility function to find the
    // maximum area
    return getMaxAreaRec(hist, st, n, 0, n-1);
}
 
// Driver program to test above functions
int main()
{
    int hist[] =  {6, 1, 5, 4, 5, 2, 6};
    int n = sizeof(hist)/sizeof(hist[0]);
    cout << "Maximum area is " << getMaxArea(hist, n);
    return 0;
}


Java




// A Divide and Conquer Program to find maximum rectangular area in a histogram
import java.util.*;
 
class GFG{
  static int[] hist;
  static int[] st;
   
  // A utility function to find minimum of three integers
  static int max(int x, int y, int z)
  return Math.max(Math.max(x, y), z); }
 
  // A utility function to get minimum of two numbers in hist[]
  static int minVal(int i, int j)
  {
    if (i == -1) return j;
    if (j == -1) return i;
    return (hist[i] < hist[j])? i : j;
  }
 
  // A utility function to get the middle index from corner indexes.
  static int getMid(int s, int e)
  {   return s + (e -s)/2; }
 
  /*  A recursive function to get the index of minimum value in a given range of
    indexes. The following are parameters for this function.
 
    hist   -. Input array for which segment tree is built
    st    -. Pointer to segment tree
    index -. Index of current node in the segment tree. Initially 0 is
             passed as root is always at index 0
    ss & se  -. Starting and ending indexes of the segment represented by
                 current node, i.e., st[index]
    qs & qe  -. Starting and ending indexes of query range */
  static int RMQUtil( int ss, int se, int qs, int qe, int index)
  {
    // If segment of this node is a part of given range, then return the
    // min of the segment
    if (qs <= ss && qe >= se)
      return st[index];
 
    // If segment of this node is outside the given range
    if (se < qs || ss > qe)
      return -1;
 
    // If a part of this segment overlaps with the given range
    int mid = getMid(ss, se);
    return minVal( RMQUtil(ss, mid, qs, qe, 2*index+1),
                  RMQUtil( mid+1, se, qs, qe, 2*index+2));
  }
 
  // Return index of minimum element in range from index qs (query start) to
  // qe (query end).  It mainly uses RMQUtil()
  static int RMQ(  int n, int qs, int qe)
  {
    // Check for erroneous input values
    if (qs < 0 || qe > n-1 || qs > qe)
    {
      System.out.print("Invalid Input");
      return -1;
    }
 
    return RMQUtil(  0, n-1, qs, qe, 0);
  }
 
  // A recursive function that constructs Segment Tree for hist[ss..se].
  // si is index of current node in segment tree st
  static int constructSTUtil(int ss, int se, int si)
  {
    // If there is one element in array, store it in current node of
    // segment tree and return
    if (ss == se)
      return (st[si] = ss);
 
    // If there are more than one elements, then recur for left and
    // right subtrees and store the minimum of two values in this node
    int mid = getMid(ss, se);
    st[si] =  minVal( constructSTUtil( ss, mid,  si*2+1),
                     constructSTUtil( mid+1, se,  si*2+2));
    return st[si];
  }
 
  /* Function to construct segment tree from given array. This function
   allocates memory for segment tree and calls constructSTUtil() to
   fill the allocated memory */
  static void constructST(int n)
  {
    // Allocate memory for segment tree
    int x = (int)(Math.ceil(Math.log(n))); //Height of segment tree
    int max_size = 2*(int)Math.pow(2, x) - 1; //Maximum size of segment tree
    st = new int[max_size*2];
 
    // Fill the allocated memory st
    constructSTUtil( 0, n-1, 0);
 
    // Return the constructed segment tree
    // return st;
  }
 
  // A recursive function to find the maximum rectangular area.
  // It uses segment tree 'st' to find the minimum value in hist[l..r]
  static int getMaxAreaRec( int n, int l, int r)
  {
    // Base cases
    if (l > r)  return Integer.MIN_VALUE;
    if (l == r)  return hist[l];
 
    // Find index of the minimum value in given range
    // This takes O(Logn)time
    int m = RMQ( n, l, r);
 
    /* Return maximum of following three possible cases
       a) Maximum area in Left of min value (not including the min)
       a) Maximum area in right of min value (not including the min)
       c) Maximum area including min */
    return max(getMaxAreaRec( n, l, m - 1),
               getMaxAreaRec( n, m + 1, r),
               (r - l + 1)*(hist[m]) );
  }
 
  // The main function to find max area
  static int getMaxArea( int n)
  {
    // Build segment tree from given array. This takes
    // O(n) time
    constructST(n);
 
    // Use recursive utility function to find the
    // maximum area
    return getMaxAreaRec( n, 0, n - 1);
  }
 
  // Driver program to test above functions
  public static void main(String[] args)
  {
    int[] a = {6, 1, 5, 4, 5, 2, 6};
    int n = a.length;
    hist = new int[n];
 
    hist = a;
    System.out.print("Maximum area is " +  getMaxArea(n));
  }
}
 
// This code is contributed by Rajput-Ji


Python3




# Python3 program for range minimum 
# query using segment tree
 
# modified to return index of minimum instead of minimum itself
# for further reference link
 
#-------------------------------------------------------------------------
from math import ceil,log2; 
   
# A utility function to get 
# minimum of two numbers 
def minVal(hist,x, y) :
    if x==-1:
        return y
    if y==-1:
        return x
    return x if (hist[x] < hist[y]) else y; 
   
# A utility function to get the 
# middle index from corner indexes. 
def getMid(s, e) :
    return s + (e - s) // 2
   
""" A recursive function to get the 
minimum value in a given range 
of array indexes. The following 
are parameters for this function. 
   
    st --> Pointer to segment tree 
    index --> Index of current node in the 
        segment tree. Initially 0 is 
        passed as root is always at index 0 
    ss & se --> Starting and ending indexes 
                of the segment represented 
                by current node, i.e., st[index] 
    qs & qe --> Starting and ending indexes of query range """
def RMQUtil( hist,st, ss, se, qs, qe, index) :
   
    # If segment of this node is a part 
    # of given range, then return 
    # the min of the segment 
    if (qs <= ss and qe >= se) :
        return st[index]; 
   
    # If segment of this node 
    # is outside the given range 
    if (se < qs or ss > qe) :
        return -1
   
    # If a part of this segment 
    # overlaps with the given range 
    mid = getMid(ss, se); 
    return minVal(hist,RMQUtil(hist,st, ss, mid, qs, 
                          qe, 2 * index + 1), 
                  RMQUtil(hist,st, mid + 1, se,
                          qs, qe, 2 * index + 2)); 
   
# Return minimum of elements in range 
# from index qs (query start) to 
# qe (query end). It mainly uses RMQUtil() 
def RMQ( hist,st, n, qs, qe) : 
   
    # Check for erroneous input values 
    if (qs < 0 or qe > n - 1 or qs > qe) :
       
        print("Invalid Input"); 
        return -1
       
    return RMQUtil(hist,st, 0, n - 1, qs, qe, 0); 
   
# A recursive function that constructs 
# Segment Tree for array[ss..se]. 
# si is index of current node in segment tree st 
def constructSTUtil(hist, ss, se, st, si) :
   
    # If there is one element in array, 
    # store it in current node of 
    # segment tree and return 
    if (ss == se) :
   
        st[si] = ss; 
        return st[si]; 
   
    # If there are more than one elements, 
    # then recur for left and right subtrees 
    # and store the minimum of two values in this node 
    mid = getMid(ss, se); 
    st[si] = minVal(hist,constructSTUtil(hist, ss, mid,
                                    st, si * 2 + 1),
                    constructSTUtil(hist, mid + 1, se,
                                    st, si * 2 + 2)); 
       
    return st[si]; 
   
"""Function to construct segment tree 
from given array. This function allocates 
memory for segment tree and calls constructSTUtil()
to fill the allocated memory """
def constructST( hist, n) :
   
    # Allocate memory for segment tree 
   
    # Height of segment tree 
    x = (int)(ceil(log2(n))); 
   
    # Maximum size of segment tree 
    max_size = 2 * (int)(2**x) - 1
    
    st = [0] * (max_size); 
   
    # Fill the allocated memory st 
    constructSTUtil(hist, 0, n - 1, st, 0); 
   
    # Return the constructed segment tree 
    return st; 
   
 
#----------------------------------------------------------------
 
# main program
# Python3 program using Divide and Conquer
# to find maximum rectangular area under a histogram
 
 
def max_area_histogram(hist):
    area=0
    #initialize area
     
    st = constructST(hist, len(hist))
    # construct the segment tree
     
    try:
        # try except block is generally used in this way
        # to suppress all type of exceptions raised.
         
        def fun(left,right):
             
        # this function "fun" calculates area
        # recursively between indices left and right
             
            nonlocal area
             
            # global area won't work here as
            # variable area is defined inside function
            # not in main().
              
            if left==right:
                return
            # the recursion has reached end
              
             
            index = RMQ(hist,st, len(hist), left, right-1)
            # RMQ function returns index
            # of minimum value
            # in the range of [left,right-1]
            # can also be found by using min() but
            # results in O(n) instead of O(log n) for traversing
             
            area=max(area,hist[index]*(right-left))
            # calculate area with minimum above
             
            fun(index+1,right)
            fun(left,index)
            # initiate further recursion
              
            return
                  
        fun(0,len(hist))
        # initializes the recursion
         
        return(area)
        # return the max area to calling function
        # in this case "print"
          
    except:
        pass
     
# Driver Code
hist = [6, 2, 5, 4, 5, 1, 6]
print("Maximum area is"
       max_area_histogram(hist))
   
# This code is contributed 
# by Vishnudev C.


C#




// C# code to implement the approach
using System;
using System.Numerics;
using System.Collections.Generic;
 
public class GFG {
 
  static int[] hist;
  static int[] st;
   
  // A utility function to find minimum of three integers
  static int max(int x, int y, int z)
  return Math.Max(Math.Max(x, y), z); }
 
  // A utility function to get minimum of two numbers in hist[]
  static int minVal(int i, int j)
  {
    if (i == -1) return j;
    if (j == -1) return i;
    return (hist[i] < hist[j])? i : j;
  }
 
  // A utility function to get the middle index from corner indexes.
  static int getMid(int s, int e)
  {   return s + (e -s)/2; }
 
  /*  A recursive function to get the index of minimum value in a given range of
    indexes. The following are parameters for this function.
 
    hist   -. Input array for which segment tree is built
    st    -. Pointer to segment tree
    index -. Index of current node in the segment tree. Initially 0 is
             passed as root is always at index 0
    ss & se  -. Starting and ending indexes of the segment represented by
                 current node, i.e., st[index]
    qs & qe  -. Starting and ending indexes of query range */
  static int RMQUtil( int ss, int se, int qs, int qe, int index)
  {
    // If segment of this node is a part of given range, then return the
    // min of the segment
    if (qs <= ss && qe >= se)
      return st[index];
 
    // If segment of this node is outside the given range
    if (se < qs || ss > qe)
      return -1;
 
    // If a part of this segment overlaps with the given range
    int mid = getMid(ss, se);
    return minVal( RMQUtil(ss, mid, qs, qe, 2*index+1),
                  RMQUtil( mid+1, se, qs, qe, 2*index+2));
  }
 
  // Return index of minimum element in range from index qs (query start) to
  // qe (query end).  It mainly uses RMQUtil()
  static int RMQ(  int n, int qs, int qe)
  {
    // Check for erroneous input values
    if (qs < 0 || qe > n-1 || qs > qe)
    {
      Console.Write("Invalid Input");
      return -1;
    }
 
    return RMQUtil(  0, n-1, qs, qe, 0);
  }
 
  // A recursive function that constructs Segment Tree for hist[ss..se].
  // si is index of current node in segment tree st
  static int constructSTUtil(int ss, int se, int si)
  {
    // If there is one element in array, store it in current node of
    // segment tree and return
    if (ss == se)
      return (st[si] = ss);
 
    // If there are more than one elements, then recur for left and
    // right subtrees and store the minimum of two values in this node
    int mid = getMid(ss, se);
    st[si] =  minVal( constructSTUtil( ss, mid,  si*2+1),
                     constructSTUtil( mid+1, se,  si*2+2));
    return st[si];
  }
 
  /* Function to construct segment tree from given array. This function
   allocates memory for segment tree and calls constructSTUtil() to
   fill the allocated memory */
  static void constructST(int n)
  {
    // Allocate memory for segment tree
    int x = (int)(Math.Ceiling(Math.Log(n))); //Height of segment tree
    int max_size = 2*(int)Math.Pow(2, x) - 1; //Maximum size of segment tree
    st = new int[max_size*2];
 
    // Fill the allocated memory st
    constructSTUtil( 0, n-1, 0);
 
    // Return the constructed segment tree
    // return st;
  }
 
  // A recursive function to find the maximum rectangular area.
  // It uses segment tree 'st' to find the minimum value in hist[l..r]
  static int getMaxAreaRec( int n, int l, int r)
  {
    // Base cases
    if (l > r)  return Int32.MinValue;
    if (l == r)  return hist[l];
 
    // Find index of the minimum value in given range
    // This takes O(Logn)time
    int m = RMQ( n, l, r);
 
    /* Return maximum of following three possible cases
       a) Maximum area in Left of min value (not including the min)
       a) Maximum area in right of min value (not including the min)
       c) Maximum area including min */
    return max(getMaxAreaRec( n, l, m - 1),
               getMaxAreaRec( n, m + 1, r),
               (r - l + 1)*(hist[m]) );
  }
 
  // The main function to find max area
  static int getMaxArea( int n)
  {
    // Build segment tree from given array. This takes
    // O(n) time
    constructST(n);
 
    // Use recursive utility function to find the
    // maximum area
    return getMaxAreaRec( n, 0, n - 1);
  }
 
// Driver Code
public static void Main(string[] args)
{
    int[] a = {6, 1, 5, 4, 5, 2, 6};
    int n = a.Length;
    hist = new int[n];
 
    hist = a;
    Console.WriteLine("Maximum area is " +  getMaxArea(n));
}
}


Javascript




// A utility function to get minimum of two numbers
function minVal(hist, x, y) {
    if (x == -1) return y;
    if (y == -1) return x;
    return hist[x] < hist[y] ? x : y;
}
 
// A utility function to get the middle index from corner indexes
function getMid(s, e) {
    return s + Math.floor((e - s) / 2);
}
 
/* A recursive function to get the minimum value in a given range
   of array indexes. The following are parameters for this function.
    
   st --> Pointer to segment tree
   index --> Index of current node in the segment tree. Initially 0 is passed as root is always at index 0
   ss & se --> Starting and ending indexes of the segment represented by current node, i.e., st[index]
   qs & qe --> Starting and ending indexes of query range */
function RMQUtil(hist, st, ss, se, qs, qe, index) {
    // If segment of this node is a part of given range, then return the min of the segment
    if (qs <= ss && qe >= se) return st[index];
 
    // If segment of this node is outside the given range
    if (se < qs || ss > qe) return -1;
 
    // If a part of this segment overlaps with the given range
    const mid = getMid(ss, se);
    return minVal(hist, RMQUtil(hist, st, ss, mid, qs, qe, 2 * index + 1), RMQUtil(hist, st, mid + 1, se, qs, qe, 2 * index + 2));
}
 
// Return minimum of elements in range from index qs (query start) to qe (query end). It mainly uses RMQUtil()
function RMQ(hist, st, n, qs, qe) {
    // Check for erroneous input values
    if (qs < 0 || qe > n - 1 || qs > qe) {
        console.log("Invalid Input");
        return -1;
    }
    return RMQUtil(hist, st, 0, n - 1, qs, qe, 0);
}
 
// A recursive function that constructs Segment Tree for array[ss..se].
// si is index of current node in segment tree st
function constructSTUtil(hist, ss, se, st, si) {
    // If there is one element in array, store it in current node of segment tree and return
    if (ss == se) {
        st[si] = ss;
        return st[si];
    }
 
    // If there are more than one elements, then recur for left and right subtrees
    // and store the minimum of two values in this node
    const mid = getMid(ss, se);
    st[si] = minVal(hist, constructSTUtil(hist, ss, mid, st, si * 2 + 1), constructSTUtil(hist, mid + 1, se, st, si * 2 + 2));
    return st[si];
}
 
/* Function to construct segment tree from given array. This function allocates
   memory for segment tree and calls constructSTUtil() to fill the allocated memory */
function constructST(hist, n) {
    // Allocate memory for segment tree
    // Height of segment tree
    const x = Math.ceil(Math.log2(n));
    // Maximum size of segment tree
    const max_size = 2 * Math.pow(2, x) - 1;
 
const st = new Array(max_size).fill(0);
constructSTUtil(hist, 0, n - 1, st, 0);
 
// Return the constructed segment tree
return st;
}
 
/**
 
Python: def max_area_histogram(hist)
 
JavaScript equivalent:
*/
function max_area_histogram(hist) {
let area = 0;
// initialize area
 
const st = constructST(hist, hist.length);
// construct the segment tree
 
function fun(left, right) {
// this function "fun" calculates area
// recursively between indices left and right
    if (left === right) {
     return;
 }
 // the recursion has reached end
 
 const index = RMQ(hist, st, hist.length, left, right - 1);
 // RMQ function returns index of minimum value
 // in the range of [left,right-1]
 // can also be found by using Math.min() but
 // results in O(n) instead of O(log n) for traversing
 
 area = Math.max(area, hist[index] * (right - left));
 // calculate area with minimum above
 
 fun(index + 1, right);
 fun(left, index);
 // initiate further recursion
}
 
fun(0, hist.length);
 
return area;
}
 
/* Example usage */
console.log("Maximum area is: " +max_area_histogram([6, 2, 5, 4, 5, 1, 6])); // expected output: 12


Output

Maximum area is 12

Time Complexity: O(N log N)

Auxiliary Space: O(N)

This problem can be solved in linear time. See below set 2 for linear time solution. 
Linear time solution for Largest Rectangular Area in a Histogram



Last Updated : 23 Feb, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads