题目如下:
给定一个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~