找出边界为1的最大子矩阵

题目如下:

给定一个N*N的矩阵,在这个矩阵中,只有0和1两种值,返回边框全是1的最大正方形的边长长度
例如:
{0,1,1,1,1},
{0,1,0,0,1},
{0,1,0,0,1},
{0,1,1,1,1},
{0,1,0,1,1},

其中 ,边框全是1的最大正方形的大小是4*4, 返回4。

输入在第一行给出一个N,代表这个矩阵的边长,随后给出N行,每行给出N个数组成该矩阵。

输出在一行给出边框全是1的最大正方形的边长长度。

思路:

首先考虑遍历二维数组爆搜。

列举每一个坐标点(时间复杂度n*n),注意点在于不能再遍历的同时定规模,而应该先定规模再遍历。即如果题给矩阵边长为N,则先考虑是否有边长为N的子矩阵满足要求,这一步在遍历前做(时间复杂度为n)。

遍历的同时要检验四条边是否全为1(时间复杂度为4n),然后就是注意边界条件了。边界的注意点主要有两个:1.当前遍历到的横/纵坐标加上n超出了题给矩阵的边界 2.检验4条边是否都为1时是否要取等。

这种解法总的时间复杂度为n^4,细节见代码。

#include <bits/stdc++.h>
using namespace std;
int a[1005][1005]; 
int f(int a[][1005],int N);
int main() { 
	int N;
	cin >> N;
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
			scanf("%d",&a[i][j]);
	cout << f(a,N) << endl;
	return 0;
}

int f(int a[][1005],int N) {
	int n = N;
	//从可能形成的最大边界位为1的方阵开始寻找,因为如果从小开始找,万一后边有更大的就被忽略了 
	while(n) {
		for(int i=0;i<N;i++) {
			if(i+n>N) break;//如果当前遍历到的横坐标加上n超出了题给矩阵的边界,就无需继续检验了 
			for(int j=0;j<N;j++) {
				int flag = 0;
				if(j+n>N) break;
				int row = i;
				int col = j;
				//检验上边 
				while(col<j+n) {//注意边界 
					if(a[row][col]==0) {
						flag = 1;//只要有0就跳出 
						break;
					}
					col++;
				}
				col--;
				//检验右边 
				if(flag) continue;
				while(row<i+n) {
					if(a[row][col]==0) {
						flag = 1;
						break;
					}
					row++;
				}
				row--;
				//检验下边 
				if(flag) continue;
				while(col>=j) {
					if(a[row][col]==0) {
						flag = 1;
						break;
					}
					col--;
				} 
				col++;
				//检验左边 
				if(flag) continue;
				while(row>=i) {
					if(a[row][col]==0) {
						flag = 1;
						break;
					}
					row--;
				}
				row++;//这行写不写不重要,因为程序执行到此处,一定产生了一个满足题意的n 
				return n;
			}
		}
		n--;
	}
	return 0;
}

(小插曲:因为在检验4条边的时候每次都要用flag标记,感觉十分繁琐,代码看上去也很冗余,想到c/c++中好像有goto的语法,就尝试goto到遍历纵坐标的那行代码,但是发现每次都会从新遍历,还要解决死循环的问题,所以并不实用。如果有更好的方法解决这个问题,欢迎一起交流。)

看到n^4,确实让人十分头大,有没有什么优化的方法呢?定规模和二维数组的遍历这两部步想要优化十分困难,那么就只能在检验上做文章了,思路如下:

定义一个三维数组用于预处理,前两维规模与a数组相同,第三维大小为2,分别记录当前坐标的左边有几个连续的1(包括自身)和下边有几个连续的1(包括自身).例如,若输入如下:

4
1 1 1 1
1 0 1 1
1 1 1 0
1 0 1 1

则模拟的结果如下:

这样检验时只需:

左上角的第一个数据(所求矩阵的上边)要>=n
左上角的第二个数据(所求矩阵的左边)要>=n
右上角的第二个数据(所求矩阵的右边)要>=n
左下角的第一个数据(所求矩阵的下边)要>=n

时间复杂度为O(1)。

细节见代码:

#include <bits/stdc++.h>
using namespace std;
#define max 1005
int help[max][max][2];
int a[max][max]; 
int solve(int a[max][max],int help[max][max][2],int N);
int check(int help[max][max][2],int i,int j,int n);
int main() {
	int N;
	cin >> N; 
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
			scanf("%d",&a[i][j]);
	//预处理 
	int row = N-1;//先处理最后一行 
	for(int j=N-1;j>=0;j--) {
		if(a[row][j]) {//是1才记录 
			if(j==N-1) help[row][j][0] = 1;//如果是最后一个数字,则第一个数据(右)计为1 
			else help[row][j][0] = help[row][j+1][0] + 1;	
			help[row][j][1] = 1;//第二个数据(下)计为1 
		}
	}
	for(int i=row-1;i>=0;i--) {//处理剩下的 
		for(int j=N-1;j>=0;j--) {
			if(a[i][j]) {//是1才记录 
				if(j==N-1) help[i][j][0] = 1;//如果是最后一个数字,则第一个数据(右)计为1 
				else help[i][j][0] = help[i][j+1][0] + 1;//如果不是最后一个数字,则第一个数据(右)= 右边的第一个数据(右)+1  
				help[i][j][1] = help[i+1][j][1] + 1;//第二个数据(下)= 下边的第二个数据(下)+1   
			}	
		}
	}
//以下代码可以打印三维数组模拟的数据 
	for(int i=0;i<N;i++) {
		for(int j=0;j<N;j++)
			printf("%d,%d\t",help[i][j][0],help[i][j][1]);
		printf("\n");
	}
	cout << solve(a,help,N) << endl;
	return 0;
}

int solve(int a[max][max],int help[max][max][2],int N) {
	int n = N;
	//从可能形成的最大边界位为1的方阵开始寻找 
	while(n) {
		for(int i=0;i<N;i++) {
			if(i+n>N) break;//如果当前遍历到的横坐标加上n超出了题给矩阵的边界,就无需继续检验了 
			for(int j=0;j<N;j++) {
				if(j+n>N) break;//如果当前遍历到的纵坐标加上n超出了题给矩阵的边界,就无需继续检验了 
				if(check(help,i,j,n))
					return n;
			}
		}
		n--;
	}
	return 0;
}

int check(int help[max][max][2],int i,int j,int n) {
	//左上角的第一个数据(所求矩阵的上边)要>=n
	//左上角的第二个数据(所求矩阵的左边)要>=n
	//右上角的第二个数据(所求矩阵的右边)要>=n
	//左下角的第一个数据(所求矩阵的下边)要>=n
	if(help[i][j][0]>=n && help[i][j][1]>=n && help[i][j+n-1][1]>=n && help[i+n-1][j][0]>=n)
		return 1;
	return 0;
}

 这样时间复杂度就降到n^3啦  ~v~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值