题目

给定一个整型矩阵 map,其中的值只有 0 和 1 两种,求其中全是 1 的所有矩形区域中,最大的矩形区域为 1 的数量。

例如:

  1    1    1    0

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

再如:

1    0    1    11    1    1    11    1    1    0

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

解答

如果矩阵的大小为 O(NxM),本题可以做到时间复杂度为 O(NxM)。解法的具体过程为:

1、矩阵的行数为N,以每一行做切割, 统计以当前行作为底的情况下,每个位置往上的1的数量。使用高度数组height来表示。

例如:

map  =  1    0    1    1              1    1    1    1              1    1    1    0

以第 1 行做切割后,height {1,0,1,1},height[j]表示目 前的底上(第 1 行),j位置往上(包括j位置)有多少连续的 1。

以第 2 行做切割后,height={2,1,2,2}, 注意到从第一行到第二行,height 数组的更新是十分方便的,即 height[j] = map[i][j]=- 0 ? 0 : height[j]+1。

以第 3 行做切割后,height= {3,2,3,0}。

2、对于每次切割, 都利用更新后的 height 数组来求出以每一行为底的情况下, 最大的矩形是什么。那么这么多次切割中,最大的那个矩形就是我们要的。

整个过程就是如下代码中的 maxRecSize 方法。步骤 2 的实现是如下代码中的 maxRecFromBottom 方法。

下面重点介绍一下步骤 2 如何快速地实现,这也是这道题最重要的部分,如果 height 数组的长度为M,那么求解步骤 2 的过程可以做到时间复杂度为 O(M)。

对于 height 数组,可以理解为一个直方图,比如{3,2,3,0}, 其实就是如下图所示的直方图。

也就是说,步骤 2 的实质是在一个大的直方图中求最大矩形的面积。如果我们能够求出以每一根柱子扩展出去的最大矩形,那么其中最大的矩形就是我们想找的。比如:

  • 第 1 根高度为 3 的柱子向左无法扩展,它的右边是 2,比 3 小,所以向右也无法扩展,则以第 1 根柱子为高度的矩形面积就是 3*1==3;
  • 第 2 根高度为 2 的柱子向左可以扩 1 个距离,因为它的左边是 3,比 2 大;右边的柱子也是 3,所以向右也可以扩 1 个距离,则以第 2 根柱子为高度的矩形面积就是 2*3==6;
  • 第 3 根高度为 3 的柱子向左没法扩展,向右也没法扩展,则以第3根柱子为高度的矩形面积就是 3*1==3;
  • 第 4 根高度为 0 的柱子向左没法扩展,向右也没法扩展,则以第4根柱子为高度的矩形面积就是 0*1==0;

所以,当前直方图中最大的矩形面积就是 6,也就是上图中虚线框住的部分。

考查每一根柱子最大能扩多大,这个行为的实质就是找到柱子左边刚比它小的柱子位置在哪里,以及右边刚比它小的柱子位置在哪里。这个过程怎么计算最快呢?用栈。

为了方便表述,我们以 height= {3,4,5,4,3,6}为例说明如何根据 height 数组求其中的最大矩形。具体过程如下:

1.生成一个栈,记为 stack,从左到右遍历 height 数组,每遍历一个位置,都会把位置压进 stack 中。

2.遍历到 height 的 0 位置,height[0]=3, 此时 stack 为空,直接将位置 0 压入栈中,此时 stack 从栈顶到栈底为{0}。

3.遍历到 height 的 1 位置,height[1]=4, 此时 stack 的栈顶为位置 0,值为 height[0]=3,又有 height[1]>height[0],那么将位置 1 直接压入 stack。这一步体现 了遍历过程中的一个关键逻辑:只有当前 i 位置的值 height[i] 大于当前栈顶位置所代表的值(height[stack.peek(0]),则 i 位置才可以压入 stack。

所以可以知道,stack 中从栈顶到栈底的位置所代表的值是依次递减,并且无重复值,此时 stack 从栈顶到栈底为{1,0}。

4.遍历到 height 的 2 位置,height[2]=5,与步骤 3 的情况完全一样,所以直接将位置 2 压入 stack,此时 stack 从栈顶到栈底为{2,1,0}。

5.遍历到 height 的 3 位置,height[3]=4,此时 stack 的栈顶为位置 2,值为 height[2]=5,又有 height[3] i 压入,并在这期间做如下处理:

1) 假设当前弹出的栈顶位置记为位置 j,弹出栈顶之后,新的栈项记为 k。然后我们开始考虑位置 j 的柱子向右和向左最远能扩到哪里。

2) 对位置 j 的柱子来说,向右最远能扩到哪里呢?

如果 height[j]>height[i],那么 i-1 位置就是向右能打到的最远位置。因为 j 之所以被弹出,就是因为遇到了第一个比位置 j 值小的位置。

如果 height[j]==height[i],那么 i-1 位置不一定是向右能扩到的最远位置,只是起码能扩到的位置。那怎么办呢?

可以肯定的是,在这种情况下,i 位置的柱子向左必然也可以扩到 j 位置。也就是说,j 位置的柱子扩出来的最大矩形和 i 位置的柱子扩出来的最大矩形是同一个。

所以,此时可以不再计算 j 位置的柱子能扩出来的最大矩形,因为位置 i 肯定要压入到栈中,那就等位置 i 弹出的时候再说。

3) 对位置 j 的柱子来说,向左最远能扩到哪里呢?

肯定是 k+1 位置。首先,height[+1.j-1]之 间不可能有小于或等于height[k]的值, 否则k位置早从栈里弹出了。然后因为在栈里k位置和j位置原本是相邻的,并且从栈顶到栈底的位置所代表的值是依次递减并且无重复值,所以在height[k+1..j-1]之间不可能有大于或等于 height[k],同时又小于或等于 height[j] 的,因为如果有这样的值,kj 在栈中就不可能相邻。

所以,height[k+..j-1] 之间的值必然是即大于 height[k], 又大于 height[j] 的,所以 j 位置的柱子向左最远可以扩到 k+1 位置。

4) 综上所述,j 位置的柱子能扩出来的最大矩形为 (i-k-1)*heightj。

以例子来说明:

① i==3,height[3]=4, 此时 stack 的栈顶为位置 2,值为 height[2]=5, 故 height[3]<=height[2],所以位置 2 被弹出 (j==2),当前栈顶变为 1 (k==1)。位置 2 的柱子扩出来的最大矩形面积为 (3-1-1)*5==5。

② i==3, height[3]=4, 此时 stack 的栈顶为位置 1,值为 height[1]=4, 故 height[3]<=height[1],所以位置 1 被弹出 (j==1),当前栈顶变为 1 (k= 0)。位置 1 的柱子扩出来的最大矩形面积为 (3-0-1)*4- =8,这个值实际上是不对的(偏小),但在位置 3 被弹出的时候是能够重新正确计算得到的。

③ i==3,height[3]=4, 此时 stack 的栈顶为位置0,值为 height(0]=3, 这时 height[3]<=height[2],所以位置0不弹出。

④ 将位置 3 压入 stack,stack 从栈顶到栈底为{3,0}。

6. 遍历到 height 的 4 位置,height[4]=3。 与步骤 5 的情况类似,以下是弹出过程:

1) i==4, height[4]=3, 此时 stack 的栈顶为位置 3,值为 height[3]=4, 故 height[4]<= height[3],所以位置 3 被弹出 (j==3), 当前栈顶变为 0 (k==0)。位置 3 的柱子扩出来的最大矩形面积为 (4-0-1)*4==12。这个最大面积也是位置 1 的柱子扩出来的最大矩形面积,在位置 1 被弹出时,这个矩形其实没有找到,但在位置 3 这里找到了。

2) i==4, height[4]=3, 此时stack 的栈顶为位置 0,值为 height[0]=3, 故 height[4]<=height[0],所以位置 0 被弹出 (j-=0), 当前没有了栈项元素,此时可以认为 k==-1。位置 0 的柱子扩出来的最大矩形面积为 (4-(-1)-1)*3-=12这个值实际上是不对的(偏小),但在位置4被弹出时是能够重新正确计算得到的。

3)栈已经为空,所以将位置4压入 stack,此时从栈顶到栈底为{4}。

7.遍历到 height 的 5 位置,height[5]=6,情况和步骤 3 类似,直接压入位置 5,此时从栈顶到栈底为{5.4}。

8.遍历结束后,stack 中仍有位置没有经历扩的过程,从栈顶到栈底为{5,4}。 此时因为height数组再往右不能扩出去,所以认为i-heigh.length= 6且越界之后的值极小,然后开始弹出留在栈中的位置:

i==6,height[6]极小,此时stack的栈顶为位置5,值为 height[5]=6, 故height[6]<= height[5],所以位置 6 被弹出 (j=- -6),当前栈顶变为 4 (k==4)。位置 5 的柱子扩出来的最大矩形面积为 (6-4-1)*6==6。

2) i==6,height[6] 极小,此时 stack 的栈顶为位置 4,值为 height[4]=3, 故 height[6]<= theight[4],所以位置 4 被弹出 (j==4), 栈空了,此时可以认为 k==-1。位置 4 的柱子扩出来的最大矩形面积为 (6-(-1)-1)*3==18。这个最大面积也是位置 0 的柱子扩出来的最大矩形面积,在位置 0 被弹出的时候,这个矩形其实没有找到,但在位置 4 这里找到了。

3)栈已经空了,过程结束。

9.整个过程结束,所有找到的最大矩形面积中 18 是最大的,所以返回 18。研究以上 9 个步骤时我们发现,任何一个位置都仅仅进出栈 1 次,所以时间复杂度为 O(M)。既然每做一次切割处理的时间复杂度为 O(M),一共做 N 次,则总的时间复杂度为 O(NxM)。

全部过程参看如下代码中的 maxRecSize 方法。9 个步骤的详细过程参看代码中的 maxRecFromBottom 方法。

public int maxRecSize (int[] [] map){    if (map == null 11 map. length == 011 map[0] .length == 0) {        return 0;    }    int maxArea = 0;    int[] height = new int [map[0] . length];    for (int i = 0; i < map.length; i++) {        for (int j = 0; j < map[0] .length; j++) {            height[j] = map[i][j] == 0 ? 0 : height[j] + 1;        }        maxArea = Math.max (maxRecF romBottom(height),maxArea) ;    }    return maxArea;}public int maxRecFromBottom(int[] height){    if (height == null 11 height. length == 0) {        return 0;    }    int maxArea = 0;    Stack stack = new Stack () ;    for (int i = 0; i < height.length; i++) {        while (!stack.isEmpty() && height[i] <= height[stack.peek()]) {            int j = stack.pop() ;            int k = stack. isEmpty() ? -1 : stack.peek() ;int curArea = (i - k一1) * height[j];            maxArea - Math. max (maxArea, curArea) ;        }        stack.push(i) ;    }    while (!stack. isEmpty()) {        int j = stack.pop ;        intk=        stack. isEmpty() ? -1 : stack.peek() ;        int curArea = (height. length - k - 1) * height[j];        maxArea = Math. max (maxArea, curArea) ;    }    return maxArea ;}

矩阵的逆矩阵怎么求_字节面试原题:求最大子矩阵的大小相关推荐

  1. 【刷题记录11】Java工程师丨字节面试真题(五)

    活动地址:CSDN21天学习挑战赛 JAVA面试练习题刷题记录 目录 一.雀魂启动 二.特征提取 三.毕业旅行问题 总结 我几乎每天都会刷题训练来使自己对各种算法随时保持一个清晰的状态.要知道眼过千遍 ...

  2. 【数据攻略】字节面试真题(含答案)+100道面试题库

    整理了一套字节的面试真题,还有100道PDF版的面试题库 一.SQL题 面试真题1: 抖音电商平台,现有一张订单表(order_info),有以下字段: order_id goods_id order ...

  3. 用 SQL 做数据分析的十大常用功能,附面试原题解答!!

    SQL,数据分析岗的必备技能,你可以不懂Python,R,不懂可视化,不懂机器学习.但SQL,你必须懂.要不然领导让你跑个数据来汇......,哦不,你不懂SQL都无法入职数据分析岗,更别说领导了. ...

  4. c++编写算法判断二叉树是否为完全二叉树_字节面试官:连这90道LeetCode算法题都不会也来面试?...

    面试大厂必刷:LeetCode算法90题 1. 买股票的最佳时机 难度级别:简单 题目: 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 如果你最多只允许完成一笔交易(即买入和卖出一 ...

  5. 不相交轮换的乘积怎么求_浅谈两种求条件极值的方法

    大家好,我是槿灵兮! 好久没发文了呢,高联考砸之后一直忙于高考复习,这次假期难得有点时间写点东东~ 看到专栏上面一位初二大佬 @一只柠檬精 写了这篇文章,原本我也有一个想写这文章的想法.索性就当此文是 ...

  6. 秋招之字节面试智力题

    1. 倒水 只有两个无刻度的水桶,一个可以装6L水,一个可以装5L水,如何在桶里装入3L的水 6L装满倒入5L,剩1L 5L倒出, 1L倒入5L 6L装满倒入5L剩2L 5L倒出,2L倒入5L 6L装 ...

  7. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 7丨字节面试真题【难度困难】

      活动介绍: 「数据仓库技术交流群」已经正式启动每日SQL打卡,帮助大家扎实基础,努力工作之余,别忘了自我提升. 欢迎报名和邀请小伙伴参与,一个人可能走得很快,但一群人会走得很远.

  8. 一道求因子之和面试算法题

    package com.project;/*一个数如果恰好等于它的因子之和,这个数就称为"完数".例如6=1+2+3.编程 找出1000以内的所有完数.(因子:除去这个数本身的其它 ...

  9. 三角形质心坐标怎么求_三角形的重心怎么求

    三角形重心是三角形三边中线的交点. 根据重心的性质,三边中线必交于一点. 所以作三角形任意两边的中线,其交点就是此三角形的重心. 1.重心到顶点的距离与重心到对边中点的距离之比为2:1. 证明一 三角 ...

最新文章

  1. 63.不同的路径II
  2. java判斷素數,算法改進 | java語言中判斷素數
  3. 增大胸围!Mr Burning带你在家全方位虐胸!
  4. 如何计算给定一个unigram语言模型_n-gram语言模型原理到实践
  5. ccs安装多版本编译器离线_windows 安装mysql多版本 主从复制
  6. springboot使用spring-data-jpa操作MySQL数据库
  7. linux中mtools工具_Linux中mtools命令起什么作用呢?
  8. vc 编译器的一些精典报错
  9. (超详细)2022年最新版java 8( jdk1.8u321)安装教程
  10. 《浪潮之巅》读书笔记
  11. day18-正则表达式
  12. 抛错java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11.XToolkit
  13. iOS开发-集成一网通支付
  14. SAMSUNG,三星,N8000升级
  15. 【开赛啦!邀你来战 】2022年“桂林银行杯”数据建模大赛暨全国大学生数学建模竞赛广西赛区热身赛
  16. 【C】指针的相关运算练习题
  17. 原来,这才是有钱人赚钱的真相
  18. 教程:从零开始 使用Python进行深度学习!
  19. python爬虫用urllib还是reques_Python爬虫之urllib.request库
  20. IDE硬盘与SATA的表示

热门文章

  1. Eclipse下把jar包放到工程lib下和通过buildpath加载有什么不同(解决找不到类的中级方法)...
  2. java自学入门心得体会 0.1
  3. python glob 模块 map函数
  4. 实施项目--为什么开发人员一直在抱怨需求变动
  5. SCOM发送邮件通知
  6. WORD 排版十技巧
  7. iptables 入门
  8. ES不香吗,为啥还要ClickHouse?
  9. 关于ArrayList的几大问题,看完还不懂来打我!
  10. 牛逼了!8000页Java 核心知识点+面试题整理,超全!