您的位置:首页 > 其它

算法题:直方图和0-1矩阵中最大矩形

2017-11-27 20:23 253 查看
前几天看到一道算法题目,看起来挺简单,就是有一个由0和1构成的矩形,然后找到这个矩形中全部元素都是1的最大子矩形,初看起来好像挺简单,但是我想半天没有想出来。上网搜了下才发现,这道题目还挺有名的,很多人都写文章探讨过,一个非常好的解法是借用一个找直方图中最大面积矩形的算法,可以用很短的代码来实现。所以我准备记述一下关于这两道题目的解法,有着一些自己的视角可供参考。

寻找直方图中的最大矩形

这道题的题目如下:

Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.



Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].



The largest rectangle is shown in the shaded area, which has area = 10 unit.

下面介绍一种用栈来解决的方法,快到可以达到O(N)。

1

首先考虑一种极端情况,所有的元素是升序的



在这种情况下我们怎么找到最大的矩形?这里先引进一种方法,我们会看到他是可以工作的。

步骤:先考察最右边也就是最大的矩形,它所能够组成的最大矩形就是自己,然后考虑倒数第二个,它所能够组成的最大矩形是自己的面积乘以2(假设矩形的宽度都为1,图画的不标准),也就是(最大位置i-当前矩形位置k+1)

如下图所示



再往前数以此类推直到最小的矩形,最后所有得到的面积中的最大值就是所要求的面积。这个算法的正确性是很显然的。

有了上面那个极端情况的铺垫之后,我们理解真正的算法就简单多了。

在完整的算法中,我们要用到栈这种数据结构,对应于上面的情况,栈满了之后的不断pop(),就对应与从最右边不断往左计算的过程。但是,问题是显然的,碰到不是升序的情况怎么办?其实也简单,我们要把问题转化为已知问题,没有升序的情况,我们构造出这种情况:



观察上面这种情况,我按照算法的逻辑走一遍,首先我将0压入栈(这里操作的是矩形的编号),然后比较1号矩形和0号的大小,结果比它大,那就把1压入栈,以后类推。假设现在轮到操作五号矩形了,结果发现5号比4号小。那么首先4号矩形就不可能和右边的矩形形成更大的矩形了,他能够形成的最大矩形就是它自己,所以完全可以将它弹出不用管他,也可以理解为我们需要不断把“高个”的矩形弹出,让剩下的在栈里面的矩形能够和5号这个还在外面呆着的矩形组成一组升序的矩形,然后就能够继续添加新的元素直到所有矩形都添加进去,如果所有矩形都压进栈里面栈里面是个什么情况?没错!所有序号对应的矩形都是升序的!!,然后我们就能够按照升序的经典情况处理所有的矩形。不过好像还是有点不对,有些矩形弹出了,最后算的结果对不对呢?是对的!还看上面的那幅图,假设就是只有这6个矩形,当我们要加入5的时候发现情况不对,所以就连续把4号和3号弹出,这两个是可以算出最大矩形的,然后2号矩形比5号小,那就把5号入栈,然后没了,接着5号的最大矩形就是它自己,2号的呢,用序号算(2号的面积*(5-2+1))。3,4两个矩形虽然弹出了,但是对序号的影响还在,也保证了最终结果的正确性。还有一种特殊情况就是最后一个弹出栈的矩形一定那个是最小的,所以把它的面积乘以总的矩形数就行。

综上可以看出,这种算法对整个矩形高度的数组,一次压入,一次弹出,所以总的时间成本是O(N),代码放在后面,原理上是和上面一样的,只是具体的序号方面有一点差异。

0-1矩阵中的最大矩形

0 0 1 1 0 -> 0 0 1 1 0

0 0 1 1 0 -> 0 0 2 2 0

1 1 0 0 0 -> 1 1 0 0 0

1 1 1 0 0 -> 2 2 1 0 0

如同上面左图的矩形怎样找出最大的矩形?,有了上面的算法,这道题就简单多了,我们先对矩形做一个处理,第一行不变,从第二行开始,如果一个元素为0,那就不变,不为0,就变成上面的元素加1。总的作用就是统计从自己开始算上方有多少个连续的的1。然后对处理后的矩形的每一行运行上面的寻找直方图中最大连续矩形的算法,那么很显然这么求出来的就是以这一行为底最大的连续矩形,对所有行求完之后就能够得到整个矩阵的最大矩形。易知时间成本为O(MN)。

下面是Java代码:

public class RectangleSolution {
public int LargestRectangleArea(int[] height){
if (height.length==0) return 0;
Stack<Integer> stack = new Stack<Integer>();
int i=1, max = height[0];
stack.push(0);

while(i< height.length||(i==height.length&& !stack.isEmpty()) ){
if(i!=height.length && (stack.isEmpty() ||height[i] >= height[stack.peek()])){
stack.push(i);
i++;
}
else {
int top  = height[stack.pop()];
int currMax = !stack.isEmpty()? top *(i - stack.peek()-1): top *i;
max = Math.max(currMax, max);
}
}

return max;
}

public int MaximalRectangle(int[][] rec){
int[] h = new int[rec[0].length];
int m = rec.length;
int n = rec[0].length;
int max=0;
for (int i=0;i<m;i++){
for (int j=0;j<n;j++){
if (i==0)
h[j] =rec[i][j];
else
h[j] = rec[i][j]==0? 0:rec[i-1][j]+1;
}
max = Math.max(max, LargestRectangleArea(h));

}

return max;

}

public static void main(String[] args) {
int[][] rec = {{0,0,1,1,0,1},{0,0,1,1,0,1},{1,1,0,0,0,1},{1,1,1,0,0,1},{1,1,1,0,0,1}};
int[] test ={2,2,1,0,0};
RectangleSolution rSolution = new RectangleSolution();
int max = rSolution.MaximalRectangle(rec);
int max2 = rSolution.LargestRectangleArea(test);
System.out.println(max);
System.out.println(max2);

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息