数据结构与算法笔记: 最大红矩形分析

最大红矩形

问题描述

  • 有一个 n ∗ m n*m nm 的棋盘,棋盘上的每个点都是红的或绿的
  • 你需要找出一个面积最大的矩形区域,使得其中没有绿的格子

输入格式

  • 第一行 2 个正整数 n,m,描述棋盘尺寸
  • 接下来 n 行描述这个棋盘,每行 m 个字符,每个字符为 . 或 X,其中 . 表示这个位置是红色的,X 表示这个位置是绿色的

输出格式

  • 一行一个整数,表示最大面积。

数据范围

  • 对于 30% 的数据,n,m<=100
  • 对于 60% 的数据,n,m<=400
  • 对于 100% 的数据,n,m<=1,000

关键算法实现

总体分析

  • n ∗ m n*m nm的最大红矩形面积可以转换为:
    • 求1*m的直方图的面积
    • 再求n遍不同高度的直方图的面积
  • 关键是如何在每一行都讲求以便最大直方图面积
    • 从第1行枚举到第n行, 且用 h i h_i hi表示第i列的高度,那么,当从第k-1行到k行的时候,有: h i = { h i + 1 c o l o r k , i = R e d 0 c o l o r k , i = G r e e n h_i = \left \{ \begin{array}{cccc} h_i + 1 & color_{k,i} = Red \\ 0 & color_{k,i} = Green \end{array} \right. hi={hi+10colork,i=Redcolork,i=Green
    • 高度得到之后,直接套用最大直方图的算法即可!

1 ) 算法1:时间复杂度为: O ( n 4 ) O(n^4) O(n4)的实现

// n层最大直方图, 蛮力做法
// n, m:意义如题
// matrix:一个字符串数组,用来记录所给矩形,matrix[i][j]表示所给矩形第i行第j列的字符(下标均从0开始)
// 返回值:题目所求答案,即最大矩形面积
int getResult(int n, int m, string *matrix) {
    /* 请在这里设计你的算法,并将答案返回 */
    int ans = 0; // 记录答案
    int *height = new int[m + 2](); // 存放高度的数组, 是一个长度空间为:m+2的数组, 左右增加两个高度为0的哨兵
    stack<int> myStack; // 单调栈,记录的是在height数组中的位置(索引),栈顶元素所对应的高度是最高的。

    // 算法开始,进行每一行的遍历,k表示当前所枚举的是哪一行
    for (int k = 0; k < n; ++k) {
        // 1. 对当前行的每一列进行遍历,更新当前行和当前列的高度
        for (int i = 1; i<=m; ++i) {
            // 注意遍历的i是从[1,m], 更新的i要做-1操作,索引从0开始
            if(matrix[k][i-1] == '.') {
                // height[i] = height[i] + 1; // 如果匹配到了红色,那么高度自增1
                // height[i] += 1;
                height[i]++;
            }
            else {
                // 匹配到绿色,那么高度降为0
                height[i] = 0;
            }
        }
        // 对每行存放的索引栈进行重置操作
        while (!myStack.empty()) {
            myStack.pop();
        }
        // 初始化栈,压入0,表示对卡位点占位,因为height[0] = 0, 
        myStack.push(0);

        // 第一层循环枚举直方图左边界位置
        for (int a = 1; a <= m; ++a) {
            // 第二层循环枚举直方图右边界位置
            for (int b = a; b <= m; ++b) {
                // 要求,保证所有 h[i] 不超过 32767,记录最小高度, 这里只做数据的初始化,取一个比最大值大的值即可
                int minH = 50000;
                // 从a到b更新最小高度,这里的高要求是最矮的,木桶效应
                for (int c = a; c <= b; ++c) {
                    minH = min(minH, height[c]); // 更新最小高度
                }
                ans = max(ans, (b - a + 1) * minH); // 更新最大面积
            }
        }
    }

    delete[] height;
    return ans;
}

2 ) 算法2:时间复杂度为: O ( n 3 ) O(n^3) O(n3)的实现卡位

// n层最大直方图, 卡位做法
// n, m:意义如题
// matrix:一个字符串数组,用来记录所给矩形,matrix[i][j]表示所给矩形第i行第j列的字符(下标均从0开始)
// 返回值:题目所求答案,即最大矩形面积
int getResult(int n, int m, string *matrix) {
    /* 请在这里设计你的算法,并将答案返回 */
    int ans = 0; // 记录答案
    int *height = new int[m + 2](); // 存放高度的数组, 是一个长度空间为:m+2的数组, 左右增加两个高度为0的哨兵
    stack<int> myStack; // 单调栈,记录的是在height数组中的位置(索引),栈顶元素所对应的高度是最高的。

    // 算法开始,进行每一行的遍历,k表示当前所枚举的是哪一行
    for (int k = 0; k < n; ++k) {
        // 1. 对当前行的每一列进行遍历,更新当前行和当前列的高度
        for (int i = 1; i<=m; ++i) {
            // 注意遍历的i是从[1,m], 更新的i要做-1操作,索引从0开始
            if(matrix[k][i-1] == '.') {
                height[i]++; // 如果匹配到了红色,那么高度自增1
            }
            else {
                // 匹配到绿色,那么高度降为0
                height[i] = 0;
            }
        }
        // 对每行存放的索引栈进行重置操作
        while (!myStack.empty()) {
            myStack.pop();
        }
        // 初始化栈,压入0,表示对卡位点占位,因为height[0] = 0, 
        myStack.push(0);

        // 第一层循环枚举直方图所有的列, 从1~m, 表示第1列到第m列
        for (int a = 1; a <= m; ++a) {
            // 取出当前列的高度
            int curH = height[a];
            // 左卡位点
            int lo = 0;
            // 右卡位点
            int hi = 0;
            // 第二层找到当前列的左右卡位点,并计算最大矩形面积
            // 找到左卡位点
            for (lo = a - 1; lo >= 1; lo --){
                if(height[lo] < curH) {
                    break;
                }
            }
            // 找到右卡位点
            for (hi = a + 1; lo <= n; hi++){
                if(height[hi] < curH) {
                    break;
                }
            }
            // 计算当前最大面积
            ans = max(ans, (hi - lo - 1) * curH);
        }
    }

    delete[] height;
    return ans;
}

3 ) 算法3:时间复杂度为: O ( n 2 ) O(n^2) O(n2)的实现:单调栈

// n层最大直方图, 单调栈做法
// n, m:意义如题
// matrix:一个字符串数组,用来记录所给矩形,matrix[i][j]表示所给矩形第i行第j列的字符(下标均从0开始)
// 返回值:题目所求答案,即最大矩形面积
int getResult(int n, int m, string *matrix) {
    /* 请在这里设计你的算法,并将答案返回 */
    int ans = 0; // 记录答案
    int *height = new int[m + 2](); // 存放高度的数组, 是一个长度空间为:m+2的数组, 左右增加两个高度为0的哨兵
    stack<int> myStack; // 单调栈,记录的是在height数组中的位置(索引),栈顶元素所对应的高度是最高的。

    // 算法开始,进行每一行的遍历,k表示当前所枚举的是哪一行
    for (int k = 0; k < n; ++k) {
        // 1. 对当前行的每一列进行遍历,更新当前行和当前列的高度
        for (int i = 1; i<=m; ++i) {
            // 注意遍历的i是从[1,m], 更新的i要做-1操作,索引从0开始
            if(matrix[k][i-1] == '.') {
                height[i]++;  // 如果匹配到了红色,那么高度自增1
            }
            else {
                // 匹配到绿色,那么高度降为0
                height[i] = 0;
            }
        }
        // 对每行存放的索引栈进行重置操作
        while (!myStack.empty()) {
            myStack.pop();
        }
        // 初始化栈,压入0,表示对卡位点占位,因为height[0] = 0, 
        myStack.push(0);

        // 单调栈算法,循环到m+1, 因为height[m+1] = 0;
        for (int i = 1; i <= m + 1; ++i) {
            while(height[myStack.top()] > height[i]) {
                int nowHeight = height[myStack.top()];
                myStack.pop();
                ans = max(ans, (i - myStack.top() - 1) * nowHeight);
            }
            myStack.push(i);
        }
    }

    delete[] height;
    return ans;
}

通用的输入程序

int main() {
    ios::sync_with_stdio(false);  // 读入优化
    
    int n, m;
    cin >> n >> m;
    
    // 输入字符形矩阵 数组
    string *matrix = new string[n]();
    
    // 连续输入n行矩阵
    for (int i = 0; i < n; ++i)
        cin >> matrix[i];
    
    cout << getResult(n, m, matrix) << endl;
    
    delete[] matrix;
    return 0;
}

测试输入数据

  • 输入:
    4 5
    .....
    XXXXX
    .X...
    .....
    
  • 输出:6
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值