leetcode:1504. 统计全 1 子矩形的个数

题目来源

题目描述

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int numSubmat(vector<vector<int>>& mat) {

    }
};

题目解析

问题:

  • 必须以第0行为底的矩阵,其内部全是1的有多少个
  • 必须以第1行为底的矩阵,其内部全是1的有多少个
  • 必须以第2行为底的矩阵,其内部全是1的有多少个
  • 然后将上面的全部加起来,就是答案

为什么要必须以某一行作为地基呢?这样就即不会算多,也不会算少,因为矩阵必须以某一行为地基,第1行为地基的矩阵和第2行为地基的矩阵是不同的。

举个例子:
在这里插入图片描述

  • 必须以第0行为底的矩阵,其内部全是1的子矩阵有:
    • {a}{b}{a,b]
    • 一共3
  • 必须以第1行为底的矩阵,其内部全是1的有多少个
    • 先算当前行:{c}{d}{c,d}
    • 然后以当前行为地基往上增长:{a,c}{b,d}{a,b,c,d}
    • 一共有6个

那,以某一行为底的矩阵,其内部全是1的有多少个,应该怎么求呢?

  • 在求以某一行打底时,可以按照数组直方图的思路,将当前行连同上面各行的数据处理下列,形成一个直方图,然后拿着直方图数组,去求个数
  • 拿数组直方图求答案时,【单调栈】:
    • 统计每个位置index,左侧比自己小、离自己最近的元素位置 leftIndex;右侧比自己小、离自己最近的元素位置 rightIndex;
    • 则,形成的数组直方图宽度L = rightIndex - leftIndex + 1
    • 如果只考虑当前打底行,个数 = L + (L-1) + (L - 2) + … + 1 = (L * (L + 1))/2;
    • 还需要考虑直方图的高度 h ,总个数 = h * ((L * (L + 1))/2);
    • 但这样会重复,为了避免重复,这里的 h = [index] - max([leftIndex], [rightIndex])
  • 所以,计算公式 = ( (L * (L + 1))/2 ) * ( [index] - max([leftIndex], [rightIndex]) )

举个例子:假设有一个直方图: [ 5 , 1 , 4 , 2 , 1 , 1 ] [5,1,4,2,1,1] [514211]
在这里插入图片描述

维护一个栈,要求单调增长

然后遍历数组。

  • 首先遍历到0—>5,此时栈为空,因此0–>5入栈
  • 然后遍历到1—>11—>1不能直接入栈,否则破坏单调性。为了让1—>1入栈,需要0—>5先出栈,出栈的数就要结算答案
    在这里插入图片描述

因此:

  • 对于0—>5来说:
    • 左边比0—>5小的:没有,也就是不能往左扩,leftIndex =-1
    • 右边以0—>5小的:就是旁边1—>6,所以右边不能扩张了,rightIndex =1
    • 因此:必须以当前行为底(宽度为L = 1),以5作为高的矩阵,只有1个: 以0开始,到0结束
    • 因此:必须以当前行为底(宽度为L =1),以4作为高的矩阵,只有1个 : 以0开始,到0结束
    • 因此:必须以当前行为底(宽度为L = 1),以3作为高的矩阵,只有1个 : 以0开始,到0结束
    • 因此:必须以当前行为底(宽度为L = 1),以2作为高的矩阵,只有1个 : 以0开始,到0结束
    • 算不算高度为1的矩阵呢?不算,等着右边的0—>1弹出时来算
    • 综上,一个得到:width * h = 1 * 5个
      在这里插入图片描述

0—>5结算完答案之后:

  • 现在1—>1可以入栈了
  • 然后遍历数组,到了2—>42—>4可入栈
  • 然后遍历数组,到了3—>23—>2不可直接入栈,因为会破坏单调性。所以需要先将栈顶元素2—>4出栈,出栈的同时也将结算答案
    在这里插入图片描述

因此:

  • 对于2—>4来说:
    • 左边比它小的数是1—>1
    • 右边比它小的数是3—>2
    • 所以只有自己[2开始,2结束]组成一个单位
    • 因此: 以当前行为地基(宽度为1),高度为4的子矩阵有1个
    • 因此: 以当前行为地基(宽度为1),高度为3的子矩阵有1个
    • 对于:以当前行为地基(宽度为1),高度为2的子矩阵,不去算,就算现在不算,反正总有算的时候
      在这里插入图片描述

2—>4结算完答案之后:

  • 3—>2可以入栈了
  • 然后继续遍历,遍历到了4—>2了,它和栈顶元素值相同,要不要入栈呢?
    • 入栈,当前遍历的数无论如何也要入栈(因为每个数只遍历一次,如果不记录下来的话,信息会丢失)
    • 既然要入栈,那么栈顶元素就必须3—>2出栈,一般来说,出栈意味着结算答案,但是3—>2发现将自己释放的元素是4—>2,我们之间的值相同,那么我就不结算答案了,因为3—>24—>2是联通的,可以等待4—>2结算答案时一起结算
      在这里插入图片描述

现在4–>2入栈了,我们继续遍历

  • 遍历到了5—>15—>1不能直接入栈,必须将4—>2出栈,此时4---->2要结算答案
    在这里插入图片描述
  • 对于4—>2来说:
    • 左边比它小的数是:1---->1
    • 右边比它小的数是:5---->1
    • 也就是对于当前行,当前高来说,左边扩张的边界是1,右边扩张的边界是5,所以[2,4]组成的区间都是可选的区域。
    • 因此:必须以当前行为底(宽度为L = 3),以2为高度的矩阵有:
      • 以2开始,到2结束,组成一个矩阵
      • 以2开始,到3结束,组成一个矩阵
      • 以2开始,到4结束,组成一个矩阵,共L = 3个
      • 以3开始,到3结束,组成一个矩阵
      • 以3开始,到4结束,组成一个矩阵,共L - 1 = 2个
      • 以4开始,到1结束,组成一个矩阵,共L - 2 = 1个
    • 因此:必须以当前行为底(宽度为L = 3),以1为高度的矩阵有:不去算,因为它左/右边比它小的数就是1
      在这里插入图片描述

4—>2结算完答案之后,5—1入栈,此时栈中元素为

在这里插入图片描述
现在数组已经遍历完了,但是栈不为空,我们要把栈倒空

  • 对于5---->1
    在这里插入图片描述
  • 对于1----1
    在这里插入图片描述
class Solution {
    // 计算以某一行打底(形成的直方图数组height)的全1子矩阵的数量
    int process(std::vector<int> & height){
        int m = height.size();
        int count = 0;
        std::stack<int> stack; // 单调栈:栈底 -> 栈顶: 由小 -> 大
        // 每个元素依次入栈,维持单调栈的严格单调递增结构,不符合时,弹出元素,弹出即结算
        for (int i = 0; i < m; ++i) {
            // 维持单调栈的严格单调递增结构,不符合时,弹出元素,弹出即结算
            while (!stack.empty() && height[i] <= height[stack.top()]){
                int index = stack.top(); stack.pop();
                int leftIndex = stack.empty() ? -1 : stack.top();// 左侧比[index]小、离index最近的元素位置
                int rightIndex = i;   // 右侧比[index]小、离index最近的元素位置
                int wid = rightIndex - leftIndex - 1;  // 形成的直方图长度
                int hei =  height[index] - std::max(
                        leftIndex == -1 ? 0 : height[leftIndex], height[rightIndex]
                        );
                count += ((wid * (wid+1))/2) * hei; // 公式计算个数
            } 
            stack.push(i);
        }
        // 结算单调栈中最后剩下的元素,弹出元素,弹出即结算
        while (!stack.empty()) {
            int index = stack.top(); stack.pop();
            int leftIndex = stack.empty() ? -1 : stack.top();// 左侧比[index]小、离index最近的元素位置
            int rightIndex = m; // 右侧比[index]小、离index最近的元素位置
            int l = rightIndex - leftIndex - 1; // 形成的直方图长度
            // 形成的直方图的有效结算高度 h :
            int h = height[index] - (leftIndex == -1 ? 0 : height[leftIndex]);
            count += ((l * (l+1))/2) * h; // 公式计算个数
        }

        return count;
    }
public:

    int numSubmat(vector<vector<int>>& mat) {
        int m = mat.size(), n = mat[0].size();
        int ans = 0;
        std::vector<int>  height(m);  // 以每一行打底的直方图数组
        for (int i = 0; i < m; ++i) {
            // 计算以每一行打底的直方图
            for (int j = 0; j < n; ++j) {
                height[j] = mat[i][j] == 0 ? 0 : height[j] + 1;
            }
            // 计算以每一行打底时的全1子矩阵的数量
            int cnt = process(height);
            ans += cnt;
        }
        return ans;
    }
   
};

优化方法是使用数组来实现栈

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值