【leetcode热题100】最大矩形

给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

示例 1:

输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
输出:6
解释:最大矩形如上图所示。

示例 2:

输入:matrix = [["0"]]
输出:0

示例 3:

输入:matrix = [["1"]]
输出:1

解法一 暴力破解

参考这里-solution-for-your-reference>),遍历每个点,求以这个点为矩阵右下角的所有矩阵面积。如下图的两个例子,橙色是当前遍历的点,然后虚线框圈出的矩阵是其中一个矩阵。

怎么找出这样的矩阵呢?如下图,如果我们知道了以这个点结尾的连续 1 的个数的话,问题就变得简单了。

  1. 首先求出高度是 1 的矩形面积,也就是它自身的数,也就是上图以橙色的 4 结尾的 「1234」的那个矩形,面积就是 4。

  2. 然后向上扩展一行,高度增加一,选出当前列最小的数字,作为矩阵的宽,如上图,当前列中有 2 和 4 ,那么,就将 2 作为矩形的宽,求出面积,对应上图的矩形圈出的部分。

  3. 然后继续向上扩展,重复步骤 2。

按照上边的方法,遍历所有的点,以当前点为矩阵的右下角,求出所有的矩阵就可以了。下图是某一个点的过程。

以橙色的点为右下角,高度为 1。

高度为 2。

高度为 3。

代码的话,把求每个点累计的连续 1 的个数用 width 保存,同时把求最大矩形的面积和求 width融合到同一个循环中。

public int maximalRectangle(char[][] matrix) {if (matrix.length == 0) {return 0;}//保存以当前数字结尾的连续 1 的个数int[][] width = new int[matrix.length][matrix[0].length];int maxArea = 0;//遍历每一行for (int row = 0; row < matrix.length; row++) {for (int col = 0; col < matrix[0].length; col++) {//更新 widthif (matrix[row][col] == '1') {if (col == 0) {width[row][col] = 1;} else {width[row][col] = width[row][col - 1] + 1;}} else {width[row][col] = 0;}//记录所有行中最小的数int minWidth = width[row][col];//向上扩展行for (int up_row = row; up_row >= 0; up_row--) {int height = row - up_row + 1;//找最小的数作为矩阵的宽minWidth = Math.min(minWidth, width[up_row][col]);//更新面积maxArea = Math.max(maxArea, height * minWidth);}}}return maxArea;
}

时间复杂度:O(m²n)。

空间复杂度:O(mn)。

解法二

参考这里-solution-based-on-Largest-Rectangle-in-Histogram>),接下来的解法,会让这道题变得异常简单。还记得 84 题吗?求一个直方图矩形的最大面积。

大家可以先做 84 题,然后回来考虑这道题。

再想一下这个题,看下边的橙色的部分,这完全就是上一道题呀!

算法有了,就是求出每一层的 heights[] 然后传给上一题的函数就可以了。

利用上一题的栈解法。

public int maximalRectangle(char[][] matrix) {if (matrix.length == 0) {return 0;}int[] heights = new int[matrix[0].length];int maxArea = 0;for (int row = 0; row < matrix.length; row++) {//遍历每一列,更新高度for (int col = 0; col < matrix[0].length; col++) {if (matrix[row][col] == '1') {heights[col] += 1;} else {heights[col] = 0;}}//调用上一题的解法,更新函数maxArea = Math.max(maxArea, largestRectangleArea(heights));}return maxArea;
}public int largestRectangleArea(int[] heights) {int maxArea = 0;Stack<Integer> stack = new Stack<>();int p = 0;while (p < heights.length) {//栈空入栈if (stack.isEmpty()) {stack.push(p);p++;} else {int top = stack.peek();//当前高度大于栈顶,入栈if (heights[p] >= heights[top]) {stack.push(p);p++;} else {//保存栈顶高度int height = heights[stack.pop()];//左边第一个小于当前柱子的下标int leftLessMin = stack.isEmpty() ? -1 : stack.peek();//右边第一个小于当前柱子的下标int RightLessMin = p;//计算面积int area = (RightLessMin - leftLessMin - 1) * height;maxArea = Math.max(area, maxArea);}}}while (!stack.isEmpty()) {//保存栈顶高度int height = heights[stack.pop()];//左边第一个小于当前柱子的下标int leftLessMin = stack.isEmpty() ? -1 : stack.peek();//右边没有小于当前高度的柱子,所以赋值为数组的长度便于计算int RightLessMin = heights.length;int area = (RightLessMin - leftLessMin - 1) * height;maxArea = Math.max(area, maxArea);}return maxArea;
}

时间复杂度:O(mn)。

空间复杂度:O(n)。

利用上一题的解法四。

public int maximalRectangle(char[][] matrix) {if (matrix.length == 0) {return 0;}int[] heights = new int[matrix[0].length];int maxArea = 0;for (int row = 0; row < matrix.length; row++) {//遍历每一列,更新高度for (int col = 0; col < matrix[0].length; col++) {if (matrix[row][col] == '1') {heights[col] += 1;} else {heights[col] = 0;}}//调用上一题的解法,更新函数maxArea = Math.max(maxArea, largestRectangleArea(heights));}return maxArea;
}public int largestRectangleArea(int[] heights) {if (heights.length == 0) {return 0;}int[] leftLessMin = new int[heights.length];leftLessMin[0] = -1;for (int i = 1; i < heights.length; i++) {int l = i - 1;while (l >= 0 && heights[l] >= heights[i]) {l = leftLessMin[l];}leftLessMin[i] = l;}int[] rightLessMin = new int[heights.length];rightLessMin[heights.length - 1] = heights.length;for (int i = heights.length - 2; i >= 0; i--) {int r = i + 1;while (r <= heights.length - 1 && heights[r] >= heights[i]) {r = rightLessMin[r];}rightLessMin[i] = r;}int maxArea = 0;for (int i = 0; i < heights.length; i++) {int area = (rightLessMin[i] - leftLessMin[i] - 1) * heights[i];maxArea = Math.max(area, maxArea);}return maxArea;
}

时间复杂度:O(mn)。

空间复杂度:O(n)。

解法三

解法二中套用的栈的解法,我们其实可以不用调用函数,而是把栈糅合到原来求 heights 中。因为栈的话并不是一次性需要所有的高度,所以可以求出一个高度,然后就操作栈。

public int maximalRectangle(char[][] matrix) {if (matrix.length == 0) {return 0;}int[] heights = new int[matrix[0].length + 1]; //小技巧后边讲int maxArea = 0;for (int row = 0; row < matrix.length; row++) {Stack<Integer> stack = new Stack<Integer>();heights[matrix[0].length] = 0;//每求一个高度就进行栈的操作for (int col = 0; col <= matrix[0].length; col++) {if (col < matrix[0].length) { //多申请了 1 个元素,所以要判断if (matrix[row][col] == '1') {heights[col] += 1;} else {heights[col] = 0;}}if (stack.isEmpty() || heights[col] >= heights[stack.peek()]) {stack.push(col);} else {//每次要判断新的栈顶是否高于当前元素while (!stack.isEmpty() && heights[col] < heights[stack.peek()]) {int height = heights[stack.pop()];int leftLessMin = stack.isEmpty() ? -1 : stack.peek();int RightLessMin = col;int area = (RightLessMin - leftLessMin - 1) * height;maxArea = Math.max(area, maxArea);}stack.push(col);}}}return maxArea;
}

时间复杂度:O(mn)。

空间复杂度:O(n)。

里边有一个小技巧,84 题 的栈解法中,我们用了两个 while 循环,第二个 while 循环用来解决遍历完元素栈不空的情况。其实,我们注意到两个 while 循环的逻辑完全一样的。所以我们可以通过一些操作,使得遍历结束后,依旧进第一个 while 循环,从而剩下了第 2 个 while 循环,代码看起来会更简洁。

那就是 heights 多申请一个元素,赋值为 0。这样最后一次遍历的时候,栈顶肯定会大于当前元素,所以就进入了第一个 while 循环。

解法四 动态规划

参考这里,这是 leetcode Solution 中投票最高的,但比较难理解,但如果结合 84 题去想的话就很容易了。

解法二中,用了 84 题的两个解法,解法三中我们把栈糅合进了原算法,那么另一种可以一样的思路吗?不行!因为栈不要求所有的高度,可以边更新,边处理。而另一种,是利用两个数组, leftLessMin [ ] 和 rightLessMin [ ]。而这两个数组的更新,是需要所有高度的。

解法二中,我们更新一次 heights,就利用之前的算法,求一遍 leftLessMin [ ] 和 rightLessMin [ ],然后更新面积。而其实,我们求 leftLessMin [ ] 和 rightLessMin [ ] 可以利用之前的 leftLessMin [ ] 和 rightLessMin [ ] 来更新本次的。

我们回想一下 leftLessMin [ ] 和 rightLessMin [ ] 的含义, leftLessMin [ i ] 代表左边第一个比当前柱子矮的下标,如下图橙色柱子时当前遍历的柱子。rightLessMin [ ] 时右边第一个。

left 和 right 是对称关系,下边只考虑 left 的求法。

如下图,如果当前新增的层全部是 1,当然这时最完美的情况,那么 leftLessMin [ ] 根本不需要改变。

然而事实是残酷的,一定会有 0 的出现。

我们考虑最后一个柱子的更新。上一层的 leftLessMin = 1,也就是蓝色 0 的位置是第一个比它低的柱子。但是在当前层,由于中间出现了 0。所以不再是之前的 leftLessMin ,而是和上次出现 0 的位置进行比较(因为 0 一定比当前柱子小),谁的下标大,更接近当前柱子,就选择谁。上图中出现 0 的位置是 2,之前的 leftLessMin 是 1,选一个较大的,那就是 2 了。

public int maximalRectangle4(char[][] matrix) {if (matrix.length == 0) {return 0;}int maxArea = 0;int cols = matrix[0].length;int[] leftLessMin = new int[cols];int[] rightLessMin = new int[cols];Arrays.fill(leftLessMin, -1); //初始化为 -1,也就是最左边Arrays.fill(rightLessMin, cols); //初始化为 cols,也就是最右边int[] heights = new int[cols];for (int row = 0; row < matrix.length; row++) {//更新所有高度for (int col = 0; col < cols; col++) {if (matrix[row][col] == '1') {heights[col] += 1;} else {heights[col] = 0;}}//更新所有leftLessMinint boundary = -1; //记录上次出现 0 的位置for (int col = 0; col < cols; col++) {if (matrix[row][col] == '1') {//和上次出现 0 的位置比较leftLessMin[col] = Math.max(leftLessMin[col], boundary);} else {//当前是 0 代表当前高度是 0,所以初始化为 -1,防止对下次循环的影响leftLessMin[col] = -1; //更新 0 的位置boundary = col;}}//右边同理boundary = cols;for (int col = cols - 1; col >= 0; col--) {if (matrix[row][col] == '1') {rightLessMin[col] = Math.min(rightLessMin[col], boundary);} else {rightLessMin[col] = cols;boundary = col;}}//更新所有面积for (int col = cols - 1; col >= 0; col--) {int area = (rightLessMin[col] - leftLessMin[col] - 1) * heights[col];maxArea = Math.max(area, maxArea);}}return maxArea;}

时间复杂度:O(mn)。

空间复杂度:O(n)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/675561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言操作符超详细总结

文章目录 1. 操作符的分类2. 二进制和进制转换2.1 2进制转10进制2.1.1 10进制转2进制数字 2.2 2进制转8进制和16进制2.2.1 2进制转8进制2.2.2 2进制转16进制 3. 原码、反码、补码4.移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符&#xff1a;&、|、^、~6. 逗号表达式…

WPS安装mathtype教程

注意点&#xff1a;不管你电脑是64位还是32位&#xff0c;都要用32的那个文件夹里的&#xff01;以下以我的WPS和mathtype安装的路径举例。 复制&#xff1a;C:\Program Files (x86)\MathType\MathPage\32\MathPage.wll到&#xff1a;C:\Users\Administrator\AppData\Local\Ki…

从github上拉取项目到pycharm中

有两种方法&#xff0c;方法一较为简单&#xff0c;方法二用到了git bash&#xff0c;推荐方法一 目录 有两种方法&#xff0c;方法一较为简单&#xff0c;方法二用到了git bash&#xff0c;推荐方法一方法一&#xff1a;方法二&#xff1a; 方法一&#xff1a; 在github上复制…

复制和粘贴文本时剥离格式的5种方法(MacWindows)

您可能每天复制和粘贴多次。虽然它是一个非常方便的功能&#xff0c;但最大的烦恼之一就是带来了特殊的格式。从网络上获取一些文本&#xff0c;您经常会发现粘贴到文档中时&#xff0c;它保持原始样式。 我们将展示如何使用一些简单的技巧在不格式化的情况下复制和粘贴。 1.…

下载已编译的 OpenCV 包在 Visual Studio 下实现快速配置

自己编译 OpenCV 挺麻烦的&#xff0c;配置需要耗费很长时间&#xff0c;编译也需要很长时间&#xff0c;而且无法保证能全部编译通过。利用 OpenCV 官网提供的已编译的 OpenCV 库可以节省很多时间。下面介绍安装配置方法。 1. OpenCV 官网 地址是&#xff1a;https://opencv…

C++初阶:容器(Containers)vector常用接口详解

介绍完了string类的相关内容后&#xff1a;C初阶&#xff1a;适合新手的手撕string类&#xff08;模拟实现string类&#xff09; 接下来进入新的篇章&#xff0c;容器vector介绍&#xff1a; 文章目录 1.vector的初步介绍2.vector的定义&#xff08;constructor&#xff09;3.v…

WebSocket+Http实现功能加成

WebSocketHttp实现功能加成 前言 首先&#xff0c;WebSocket和HTTP是两种不同的协议&#xff0c;它们在设计和用途上有一些显著的区别。以下是它们的主要特点和区别&#xff1a; HTTP (HyperText Transfer Protocol): 请求-响应模型&#xff1a; HTTP 是基于请求-响应模型的协…

P2036 [COCI2008-2009 #2] PERKET题解

题目 Perket是一种流行的美食。为了做好Perket&#xff0c;厨师必须谨慎选择食材&#xff0c;以在保持传统风味的同时尽可能获得最全面的味道。你有n种可支配的配料。对于每一种配料&#xff0c;我们知道它们各自的酸度s和苦度b。当我们添加配料时&#xff0c;总的酸度为每一种…

自动驾驶稳步迈向商业化应用

发展自动驾驶技术&#xff0c;是建设交通强国的重要内容。近年来&#xff0c;随着人工智能、5G、大数据等新技术快速发展&#xff0c;自动驾驶技术在交通运输领域加快应用&#xff0c;实现由封闭场地测试到道路测试、由试点示范到商业试运营的快速迭代。 交通运输部日前印发《自…

跟我学c++中级篇——std::tuple的复合操作

一、std::tuple std::tuple在应用中有着独特的作用&#xff0c;它本身可以存储非同质化的数据类型&#xff0c;这个在某些场合下非常有用。std::tuple的初级应用&#xff0c;如生成和获取&#xff0c;在前面的几篇文章中已经进行了较详细的说明。但std::tuple仍然有一些复杂的…

阿里云幻兽帕鲁服务器有用过的吗?搭建简单啊

玩转幻兽帕鲁服务器&#xff0c;幻兽帕鲁Palworld多人游戏专用服务器一键部署教程&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云百科…

了解海外云手机的多种功能

随着社会的高度发展&#xff0c;海外云手机成为商家不可或缺的工具&#xff0c;为企业出海提供了便利的解决方案。然而&#xff0c;谈及海外云手机&#xff0c;很多人仍不了解其强大功能。究竟海外云手机有哪些功能&#xff0c;可以为我们做些什么呢&#xff1f; 由于国内电商竞…

Nginx方向代理和负载均衡配置

1. Nginx介绍 2.Nginx常用命令 cd /usr/local/nginx/sbin/ ./nginx 启动 ./nginx -s stop 停止 ./nginx -s quit 安全退出 ./nginx -s reload 重新加载配置文件 如果我们修改了配置文件&#xff0c;就需要重新加载。 ps aux|grep nginx 查看nginx进程3.nginx配置文件 …

记录解决duboo注册问题和投诉问题

最近项目组遇到运维一个投诉&#xff1a;我们的dubbo服务是以接口维度注册到nacos上的&#xff0c;再高峰期无法下线服务维度导致运维风险增加。针对这个问题&#xff0c;我查了下&#xff0c;确实是这样的&#xff0c;因为我们使用的dubbo是3.0以下的&#xff0c;所以注册到注…

Vue2中v-for 与 v-if 的优先级

在Vue2中&#xff0c;v-for 和 v-if 是常用的指令&#xff0c;它们在前端开发中非常有用。但是&#xff0c;当我们在同一个元素上同时使用这两个指令时&#xff0c;就需要注意它们的优先级关系了。 首先&#xff0c;让我们了解一下v-for和v-if的基本用法。 v-for 是Vue的内置…

问题:必须坚持以中国式现代化推进中华民族伟大复兴,既不走封闭僵化的老路,也不走 #媒体#知识分享

问题&#xff1a;必须坚持以中国式现代化推进中华民族伟大复兴&#xff0c;既不走封闭僵化的老路&#xff0c;也不走 A、中国特色社会主义道路 B、改革开放之路 C、改旗易帜的邪路 D、中国式现代化之路 参考答案如图所示

2024年GPT如何发展?

2023 年&#xff0c;人工智能领域最具影响的莫过于 GPT-4、ChatGPT 了。 ChatGPT 凭一己之力掀起了 AI 领域的热潮&#xff0c;火爆全球&#xff0c;似乎开启了第四次工业革命。 ChatGPT 入选《Nature》2023 年度十大人物&#xff08;Nature’s 10&#xff09;&#xff0c;这…

Git详细讲解

文章目录 一、Git相关概念二、本地分支中文件的添加 、提交2.1 文件状态2.2 创建Git仓库2.2.1 git init2.2.2 git clone 2.3 添加操作(git add)2.4 提交操作&#xff08;git commit&#xff09;2.5 撤销操作2.5.1 撤销 add操作2.5.2 撤销 commit操作2.5.3 覆盖上一次的commit操…

极智一周 | 国产CPU系列汇总、鲲鹏、飞腾、平头哥 And so on

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多技术分享 大家好&#xff0c;我是极智视界&#xff0c;带来本周的 [极智一周]&#xff0c;关键词&#xff1a;国产CPU系列汇总、鲲鹏、飞腾、平头哥 And so on。 邀您加入我的知识星球「极智视界」&#xff0c;星球目前…

【开源】JAVA+Vue.js实现社区买菜系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 数据中心模块2.1.2 菜品分类模块2.1.3 菜品档案模块2.1.4 菜品订单模块2.1.5 菜品收藏模块2.1.6 收货地址模块 2.2 可行性分析2.3 用例分析2.4 实体类设计2.4.1 菜品分类模块2.4.2 菜品档案模块2.4.3…