寻找直方图中面积最大的矩形
题目:
在柱状图中找最大的矩形:给一组非负的整数来表示一个柱状图,设计一个算法找到最大面积的能适合到柱状图内的矩形。比如,对于这组数,1 2 3 4 1 ,有两种可能的方案,一种是适合到 2 3 4 内的矩形,面积是 2*3;另一种是适合到 3 4 内的矩形,面积是 3*2。
用数学点的描述就是,找所给数组的一个连续子数组,使该子数组的最小值与数组长度乘积最大。
解题思路:
初次看此题目,可能不是很明白,现在贴张图在下,看着就清楚题目的意思了。
分析
以上面图中给出的数据为例,最大的矩形是浅黄的矩形,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; }
运行结果:
代码二:
解法是在待字闺中微信公众账号里面看到的,一个线性算法是用堆栈来保存当前可能的矩形(高度和起始位置)。从左到右扫描,对一个元素,如果
a)大于栈顶元素, push;
b)小于的话,pop所有的大于它的元素,计算面积,更新最大值。这时如果堆栈空,push一个新的元素,高度等于当前元素,起始位置为0;否则,push当前元素高度和栈顶的起始位置。
比如1 3 2 2 3这个数组,操作如下:
代码如下:
#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; }