左神算法进阶class2—单调栈应用2:求最大子矩阵

博客介绍了如何使用单调栈解决求最大子矩阵的问题。通过分析每个1作为潜在的起点,构建辅助数组help来记录向上连续1的数量,然后对每一行进行遍历,结合直方图面积计算方法找到最大矩形区域的1数量。
摘要由CSDN通过智能技术生成

左神算法进阶class2—单调栈应用2:求最大子矩阵

1.题目:单调栈应用2:求最大子矩阵

给定一个整型矩阵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。

2.分析

本题同样可以用单调栈的思想解题,左神算法进阶class2—单调栈应用:直方图中矩形的最大面积 做为本题的引子,如果上一题不了解,本题可能会看不懂,因为本题是在上一题的基础上修改得来的。
在求直方图面积中,杆子的底侧都在同一水平线,而本题不一定从底侧开始,想一个极端情况,左上角的1,它可以单独作为一个杆子进行滑动。(0用鼠标画的真的好丑!)
在这里插入图片描述
也就是说每一行的1都可以作为杆子进行滑动,好了,我们首先可以考虑设置三条平行线,第一条只包含一行,第二条包含上两行,第三条基准线相当于直方图包含所有的值。本题是不是可以分解为把三个基准中所有的杆子进行扫略,最后求最大值。
在这里插入图片描述
如果到这里还没明白的朋友还是建议看下上一题哦。
好像是可行的!但是问题又来了,对于下图中间出现的0,相当于我们的杆子被打断了,该怎么解决呢。
在这里插入图片描述
我们可以设置一个长度为列的数量的辅助数组help,表示此位置向上有多少个连续的1。首先对于最上面的水平线,单独拎出来。如下图,简化为1,0,1,1。其中第一个1表示在当前0位置,向上有1个1。那么直方图就构成了,1,0,1,1求直方图的最大面积,这一步是可解的也是容易做的,完全是上一题的部分。
在这里插入图片描述
可能大家还比较疑惑,我们再来看第二条平行线。下图蓝线为基准线,辅助数组help更新为2,1,2,2,第一个2表示在当前列,向上有连续两个1,所以值为2。同样,辅助数组就变成直方图,计算2,1,2,2的最大矩形。可是,有朋友可能会问为什么辅助数组要表示此位置向上有多少个连续的1?直接是累加不行吗?别急,请再往下看。
在这里插入图片描述
现在我们开始计算第三条基准线啦,直接累加好像是3,2,3,2,咦,为什么最后一个是0?原来我们最初的设置基准线的目的就是从基准线开始滑动的杆子,如果是0的话,当然杆子就不存在了。
在这里插入图片描述
我们实际的杆子是从基准线开始的
在这里插入图片描述
是的,在上一题中,直方图都是从底部开始的,而本题每一个1都可以作为杆子进行滑动,所以我们设置三基准线,就囊括了所有的杆子可能的滑动,而不是只在底侧进行扫略。
下图是所有的杆子及对应的直方图
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.核心代码

现在我们回头看辅助数组到底是怎么设置的,help表示此位置向上有多少个连续的1,如果当前位置是0,上面的1就没有用了,因为杆子只从当前基准进行计算,如果不是0是1呢,那么辅助数组在当前位置就是上一行辅助数组值再加一help[j] = mp[i][j] == 0 ? 0 : help[j] + 1;。因为上一行同一位置已经统计了,只需要加一即可。
我们对整个数组按行进行遍历,统计出每行的辅助数组,再调用计算同一水平线求直方图面积的函数,找到最大值就是本题的结果。

int maxRecSize(vector<vector<int> > mp)
{
	if (mp.size() == 0 || mp[0].size() == 0)	return 0;
	vector<int> help(mp[0].size());
	int maxArea = 0;
	for (int i = 0; i < mp.size(); i++)
	{
		for (int j = 0; j < mp[0].size(); j++)
		{
			help[j] = mp[i][j] == 0 ? 0 : help[j] + 1;
		}
		maxArea = max(maxRecFromBottom(help), maxArea);
	}
	return maxArea;
}

求直方图面积如下,还是那句话,看不懂请先看上一题完全解释了直方图面积的求法

//单调栈,从底到顶从小到大
int maxRecFromBottom(vector<int>& v)
{
	if (v.empty())	return 0;
	stack<int> s;
	int maxArea = 0;
	for (int i = 0; i < v.size(); i++)//遍历每个数
	{	//栈不空且当前高度小于栈中高度-->表示不能右扩需要弹出
		while (!s.empty() && v[i] < v[s.top()])
		{
			int cur = s.top();
			s.pop();
			int left = s.empty() ? -1 : s.top(); //left为左边界,若下面没有,是- 1,否则为下面的位置
			int curArea = (i - left - 1) * v[cur];;//i是右边界,i - left - 1是扩的格子长度
			maxArea = max(curArea,maxArea);
		}
		s.push(i);
	}
	while (!s.empty())
	{
		int cur = s.top();
		s.pop();
		int left = s.empty() ? -1 : s.top();
		int curArea = (v.size() - left - 1) * v[cur];
		maxArea = max(curArea, maxArea);
	}
	return maxArea;
}

4.完整代码

#include<iostream>
#include<stack>
#include<vector>
#include<algorithm>
using namespace std;

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

int maxRecSize(vector<vector<int> > mp)
{
	if (mp.size() == 0 || mp[0].size() == 0)	return 0;
	vector<int> help(mp[0].size());
	int maxArea = 0;
	for (int i = 0; i < mp.size(); i++)
	{
		for (int j = 0; j < mp[0].size(); j++)
		{
			help[j] = mp[i][j] == 0 ? 0 : help[j] + 1;
		}
		maxArea = max(maxRecFromBottom(help), maxArea);
	}
	return maxArea;
}
int main()
{
	//vector<int> v{ 6,5,2,3,4 };
	//cout << maxRecFromBottom(v);
	vector<vector<int> > mp = { { 1, 0, 1, 1 },{ 1, 1, 1, 1 },{ 1, 1, 1, 0 }};
	cout << maxRecSize(mp);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值