面试题第三弹

题目:给定一个整形矩阵Map,其中的值只有0和1两种,求其中全是1的矩阵中,最大矩阵面积区域为1的数量。

例如:

1 1 1 0
其中,最大矩形区域有3个1,所以返回3.

再如

1 0 1 1
1 1 1 1
1 1 1 0
其中,最大的矩形区域有6个1,所以返回6


思路

首先我们来了解一个数据结构,与其说是一种数据结构倒不如说是一种工具。它叫单调栈(对此有所了解的同学可以跳过这段)
单调栈的功能:找出一列数中每个数离它最近的比它小的数(分为左右两个数),而使用一个栈作为辅助空间可以将暴力遍历的时间复杂度O(n^2)降到O(n).

举个栗子

一列数为: 1 2 9 7 3 5 4
栈:st(初始为空)
1.数组遍历到 1 ,st为空,直接压入栈,continue;

2.数组遍历到 2 ,st不为空, 且st.top()小于2,符合单调栈的要求,可以压入栈,continue;

3.数组遍历到 9 , st不为空, 且st.top()小于9,符合单调栈的要求,可以压入栈,continue;

4.数组遍历到 7 , st不为空,但是st.top()大于7,不符合单调栈的要求,应该弹出9且压入7,此时9左边第一个小于9的数即st.top()(弹出9之后的st的top), 9右边第一个小于9的数就是使它弹出来的这个数即7.

此时我们得到:

左边第一个小的数 该数 右边第一个小的数
2 9 7

此时栈的结构为:

7
2
1

5.数组遍历到3 , st不为空,但是st.top()大于3 , 不符合单调栈的要求,应该弹出7且压入3
此时我们得到:

左边第一个小的数 该数 右边第一个小的数
2 9 7
2 7 3

此时栈的结构为:

扫描二维码关注公众号,回复: 7138521 查看本文章
3
2
1

6.数组遍历到5 , st不为空,且st.top()小于5,符合单调栈的要求,可以压入栈,continue;

7.数组遍历到4 , st不为空,但是st.top()大于4,不符合单调栈的要求,应该弹出5且压入4
此时我们得到:

左边第一个小的数 该数 右边第一个小的数
2 9 7
2 7 3
3 5 4

8.此时数组已经遍历完成,但st不为空,所以依次弹出栈中元素,此时栈的结构为:

4
3
2
1

最终结果如图:

左边第一个小的数 该数 右边第一个小的数
2 9 7
2 7 3
3 5 4
3 4 None
2 3 None
1 2 None
None 1 None

以上简单地模拟了一下单调栈的工作原理


Q: 所以你跟我讲了半天的单调栈对题目有什么用吗?
A: 废话,当然有用处的,不然我讲它干嘛,用处嘛,你听我一步一步道来


我们这样考虑,以每一行为底,求以该行为底的每个数能向上连续的1的个数,什么意思呢?
举个栗子:

1 0 1 0
0 1 1 1
0 1 1 1

这是一个矩阵,当以第0行为底时,该行每个数能向上连续1的结果如下:

1 0 1 0

当以第1行为底时,该行能向上连续1的结果如下:

0 1 2 1

当以第2行为底时,该行能向上连续1的结果如下:

0 2 3 2

然后我们把最后得到的 0 2 3 2 想象成一个直方图

o
o o o
o o o
0 2 3 2

(“o”表示该块可用)

显而易见可知,最大的合法矩形包含1的个数是6,是怎么看出来的呢,
比如从0开始,因为该列一个可用块都没有,所以0位置标记一个0;

然后到了2,该列有两个可用块,因为它左边为0比它小所以不能往左扩展,但可以往右扩展一直到右边界,所以它的矩形包含1的个数为2*3=6;

然后到了3,因为它左右两边的数就是离它最近的比它小的数,所以该列不能向左和向右扩展了,所以它的矩形包含1的个数为3*1=3;

然后到了2,因为它右边为边界,所以不能往右扩展了,但向左可以扩展到第一个2,所以它的矩形包含1的个数为2*3=6;

所以最大的矩形包含的1的个数为max{0,6,3,6} = 6

所以为题转化为求 0 2 3 2 这列数的每个数向左和向右的第一个比它小的数,这不就与单调栈联系上了吗。

代码区:

int maxRecFromBottom(vector<int> height)
{
    if (height.empty())
    {
        return 0;
    }
    stack<int> st;
    int maxArea = 0;
    for (int i = 0; i < height.size(); i++)
    {
        while (!st.empty() && height[i] <= height[st.top()])
        {
            int j = st.top();
            st.pop();
            int k = st.empty() ? -1 : st.top();
            int curArea = (i - k - 1) * height[j];
            maxArea = max(maxArea, curArea);
        }
        st.push(i);
    }
    while (!st.empty())
    {
        int j = st.top();
        st.pop();
        int k = st.empty() ? -1 : st.top();
        int curArea = (height.size() - k - 1) * height[j];
        maxArea = max(maxArea, curArea);
    }
    return maxArea;
}

int maxRecSize(vector<vector<int>> map)
{
    if (map.empty() || map.size() == 0 || map[0].size() == 0)
    {
        return 0;
    }
    int maxArea = 0;
    vector<int> height(map[0].size(), 0); //构造map的列数个元素
    for (int i = 0; i < map.size(); i++)
    {
        for (int j = 0; j < map[0].size(); j++)
        {

            height[j] = (map[i][j] == 0 ? 0 : height[j]+1);
            int a = map[i][j];
            int b = height[j];
        }
        maxArea = max(maxArea, maxRecFromBottom(height));
    }
    return maxArea;
}

观察代码我们发现,我们是改进了单调栈的用法的,
改进1:因为给定的数组中可能存在连续相同的数的情况,所以我们把弹出的条件改为stack.top()>=array[i],即相同的数不能往右扩展,只能往左扩展,这样也许不是太符合单调栈的条件,但这些相同的数中总会有一个可以代表他们的最大值,取该值即可。

改进2:我们压入栈的是数组的下标而不是数组的值,因为我们要计算面积,用下标方便计算且可以直接通过数组取到相应的值。

猜你喜欢

转载自www.cnblogs.com/MrSecond/p/11436607.html