寻找直方图中面积最大的矩形

题目:

在柱状图中找最大的矩形:给一组非负的整数来表示一个柱状图,设计一个算法找到最大面积的能适合到柱状图内的矩形。比如,对于这组数,1 2 3 4 1 ,有两种可能的方案,一种是适合到 2 3 4 内的矩形,面积是 2*3;另一种是适合到 3 4 内的矩形,面积是 3*2。

用数学点的描述就是,找所给数组的一个连续子数组,使该子数组的最小值与数组长度乘积最大。

解题思路:

初次看此题目,可能不是很明白,现在贴张图在下,看着就清楚题目的意思了。

choudan_algorithm130903

分析

以上面图中给出的数据为例,最大的矩形是浅黄的矩形,3*5的大小,这个结果可以一眼就从图中扫出来。当遇到程序时,这要找到一个合理的判断过程,一步步来求解出最大的矩形面积。

从左至右开始看:

首先是数字1,以1为高度的矩形,长度最大可以达到图的末尾,其矩形的面积为1*9=9。

其次是数字2,以2为高度的矩形,长度最大为7,则矩形面积为2*7=14。

再次是数字3,以3为高度的矩形,长度最大是5,则矩形面积为3*5=15。

接着是数字4,数字4之后就被下一个数字断开了,长度最大为1,则矩形面积为 4*1=4 。

后面接着数字3,小于上一个数字4,但与上上个数字一样大。 依次类推。

观察这个过程,如果要在O(n)的时间内找到最大的面积,则需要记录下来每个高度为N的矩形,其长度最大可以达到多少。这样就分为两种情况,如图中描述的,首先是高度N越来越大,其次是高度N越来越小。

1.在高度N越来越大时,其上一个数字构成的高度的矩形的最大长度则增加1,例如,2 3,高度为3时,则高度为2的矩形的长度就加1.

2.在高度N越来越小时,其上一个数字构成的高度的矩形的最大长度就不变,例如 4 3,高度为4时,其构成的矩形长度为1,到了3,矩形长度没有递增,而4之前的数字3构成的矩形的长度也加1。

有了上面这两条分析,就可以使用一个stack来存储矩形的高度和长度,其中长度会动态的变化。当遇到一个数字大于栈顶数字的时候就压入栈,小于栈顶的数字就弹出栈,在这个动态过程中,更新最大的矩形面积。

实现代码如下:

#include<iostream>
#include<iterator>
#include<stack>

using namespace std;

void print(stack<pair<int,int> > mstack)
{
    while(!mstack.empty())
    {
        cout << "<" << mstack.top().first << "," << mstack.top().second << ">" << endl;
        mstack.pop();
    }
}

int maxrectangle(int *array, int len)
{
    if(array == NULL || len <= 0)
        return 0;
    stack<pair<int,int> > mstack;//存放每个元素和其对应的长度 
    int top = 0,max = 0;
    int i = 0;
    while(i < len)
    {
        if(array[i] > top)
        {
            mstack.push(make_pair(array[i],1));//第一次出现,将元素值(高度)和长度1压入栈中 
        }
        else
        {
            /*
            pre用来记录将要加入栈中元素对于的矩形长度,因为当前要处理的元素小于栈顶元素
            表明当前元素长度可以向前延伸,有栈中有几个元素大于当前元素即表示当前元素
            矩形长度可以延伸多少 
            */
            int pre = 0; 
            while(array[i] < top){
            /*小于栈顶元素,表明栈顶元素对应的矩形不可能再延伸了,即长度不会再加了
            这时可以求出栈顶元素对应的矩形面积,在与保存的最大值比较,进行最大面积更新 
            */ 
                int time = mstack.top().second;
                pre += time;
                max = top*pre > max ? top*pre : max;//将栈顶元素对应的矩形面积与保存的最大值比较 
                mstack.pop();
                if(!mstack.empty())//栈不为空,则当前元素继续与栈顶元素比较 
                    top = mstack.top().first;
                else
                    break;
            }
            //循环退出时,如果栈不为空且栈顶元素等于当前元素,则进行矩形长度合并 
            if(!mstack.empty() && mstack.top().first == array[i])
                mstack.top().second += pre+1;//+1表示将当前元素也计算在内 
            else{//否则,将当前元素入栈 
                mstack.push(make_pair(array[i],pre+1));
            }
        }
        print(mstack);
        cout << "***********" << endl;
        top = mstack.top().first;//更新top保存的栈顶元素,准备与下一个取出的元素进行比较 
        ++i;
    }
    
    int pre = 0;
    /*
    上面while退出后,栈中元素为递增 
    如数组为 {1,2,3,4,5,4}时,此时上面程序只是对5进行了矩形面积求值,然后栈中剩下单增元素{1,2,3,4} 
    现在从栈中逐个弹出,进行max求值 
    */ 
    while(!mstack.empty())//判断栈是否为空,不为空,则表示此时栈中都是递增的 
    {
        int time = mstack.top().second;
        int top = mstack.top().first;
        pre += time;
        max = top*pre > max ? top*pre : max;//将栈顶元素对应的矩形面积与保存的最大值比较 
        mstack.pop();
                      
    }

    return max;
}


int main()
{
    //int array[] = {1,2,3,4,3,4,3,2,1};
    //int array[] = {6,0,5,0,2,7,1,2};
    int array[] = {1,2,3,4,5,4};
    int len = sizeof(array) / sizeof(int);
    copy(array,array+len,ostream_iterator<int,char>(cout," "));
    cout << endl;
    int Max = maxrectangle(array,len);
    cout<<"max:"<<Max<<endl;
    return 0;
}

运行结果:

image

 

代码二:

解法是在待字闺中微信公众账号里面看到的,一个线性算法是用堆栈来保存当前可能的矩形(高度和起始位置)。从左到右扫描,对一个元素,如果

a)大于栈顶元素, push;

b)小于的话,pop所有的大于它的元素,计算面积,更新最大值。这时如果堆栈空,push一个新的元素,高度等于当前元素,起始位置为0;否则,push当前元素高度和栈顶的起始位置。

比如1 3 2 2 3这个数组,操作如下:

image

代码如下:

#include <iostream>
#include <stack>
using namespace std;


int MaxArea(int *num, int len)
{
    if(num == NULL || len < 1)
        return 0;

    stack<int> numStack;
    stack<int> indStack;
    numStack.push(num[0]);
    indStack.push(0);
    int lastPopInd = 0;
    int maxMul = num[0];
    for(int i = 1; i < len; ++i)
    {
        if(num[i] > numStack.top())
        {
            numStack.push(num[i]);
            indStack.push(i);
        }
        else if(num[i] < numStack.top())
        {
            while(!numStack.empty() && num[i] < numStack.top())
            {
                int numPop = numStack.top();
                lastPopInd = indStack.top();
                maxMul = (numPop * (i - lastPopInd)) > maxMul ? (numPop * (i - lastPopInd)) : maxMul;
                numStack.pop();
                indStack.pop();
            }
            if(numStack.empty() || num[i] > numStack.top())
            {
                numStack.push(num[i]);
                indStack.push(lastPopInd);
            }
        }
    }
    while(!numStack.empty())
    {
        int numPop = numStack.top();
        lastPopInd = indStack.top();
        maxMul = (numPop * (len - lastPopInd)) > maxMul ? (numPop * (len - lastPopInd)) : maxMul;
        numStack.pop();
        indStack.pop();
    }
    return maxMul;
}

int main(void)
{
    int arr[] = {6,0,5,0,2,7,1,2};
    int len = sizeof(arr) / sizeof(arr[0]);
    int ret = MaxArea(arr, len);
    cout<<ret<<endl;
    return 0;
    
}
posted @ 2014-04-09 15:18  mickole  阅读(1708)  评论(0编辑  收藏  举报