题目:给定一个N*N的矩阵matrix,在这个矩阵中,只有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
思路:直接枚举,根据阶数递减扫描,每一阶都要遍历所有数组,根据每一个点扫描这个点向右向下向左向上途经的每一个数据能不能构成一个边框全是1的正方形。
代码:
1 public class MaxSquare { 2 3 public static void main(String[] args) { 4 // int[][] A = { 5 // { 0, 1, 1, 1, 1 }, 6 // { 0, 1, 0, 1, 0 }, 7 // { 0, 1, 1, 1, 1 }, 8 // { 0, 1, 1, 1, 1 }, 9 // { 0, 1, 0, 1, 1 } 10 // }; 11 int [][]A = new int[][] { 12 { 1, 1, 0, 1 }, 13 { 1, 1, 1, 1 }, 14 { 1, 1, 0, 1 }, 15 { 1, 1, 1, 1 }, 16 }; 17 System.out.println("最大子方阵的边长为:"+solve(A)); 18 } 19 20 // O(n^4) 21 // 直接枚举,根据阶数扫描,每一阶都要遍历所有数组,时间复杂度太高 22 static int solve(int [][]A){ 23 int N = A.length; 24 int n = N; 25 while(n>0){ 26 for (int i = 0; i < N; i++) { 27 if (i+n>N) break; 28 l3: // 这个语言相当于给第二层for循环命令 下面代码中的continue继续的是第二层的for循环,不是while循环 29 for (int j = 0; j < N; j++) { 30 if (j+n>N) break; 31 // 检查四个边 32 // 上边 33 int r = i,c = j; 34 while(c<j+n){ 35 if(A[r][c++]==0) continue l3; 36 } 37 c--; 38 // 右边 39 while(r<i+n){ 40 if (A[r++][c] == 0) continue l3; 41 } 42 r--; 43 // 下边 44 while(c>=j){ 45 if(A[r][c--]==0) continue l3; 46 } 47 c++; 48 // 左边 49 while(r>=i){ 50 if(A[r--][c]==0) continue l3; 51 } 52 r++; 53 return n; 54 } 55 } 56 n--; 57 } 58 return 0; 59 } 60 61 }
结果:
优化:这个算法的时间复杂度为N*N*N*(4*N),为O(N4),时间复杂度太高了,效率不行,我们可以通过对4*N的时间复杂度的优化,将整个解法的时间复杂度降到O(N3)。
思路:我们可以通过对原有矩阵进行预处理的方式,这个思路类似于动态规划中的打表法。我们可以构建一个三维数组,二维平面空间和矩阵大小相同,第三维存储两个元素,第一个元素存储包括自己以及自己右方连续的1的个数,如果自己为0,那么自己就存储为0。第二个元素存储包括自己以及自己下方连续的1的个数,如果自己为0,那么自己也存储为0。通过生成这个辅助表,就可以在判断是否是最大方阵的时候加快判断速度,可以将4个while循环去除,而通过这个辅助表只需判断一次即可,时间复杂度为O(1),那么整个算法的时间复杂度就为N*N*N了,为O(N3)。
假如矩阵为: ----------------------------- { 1, 1, 0, 1 } { 1, 1, 1, 1 } { 1, 1, 0, 1 } { 1, 1, 1, 1 } ----------------------------- 生成的辅助数组为: ----------------------------- 2,4 1,4 0,0 1,4 4,3 3,3 2,1 1,3 2,2 1,2 0,0 1,2 4,1 3,1 2,1 1,1 -----------------------------
代码:
1 public class MaxSquare_1 { 2 public static void main(String[] args) { 3 // int[][] A = { 4 // { 0, 1, 1, 1, 1 }, 5 // { 0, 1, 0, 1, 0 }, 6 // { 0, 1, 1, 1, 1 }, 7 // { 0, 1, 1, 1, 1 }, 8 // { 0, 1, 0, 1, 1 } 9 // }; 10 int [][]A = new int[][] { 11 { 1, 1, 0, 1 }, 12 { 1, 1, 1, 1 }, 13 { 1, 1, 0, 1 }, 14 { 1, 1, 1, 1 }, 15 }; 16 generateHelpRec(A); 17 System.out.println("生成的辅助数组为:"); 18 System.out.println("============================="); 19 print(rec, A.length); 20 System.out.println("============================="); 21 System.out.println("最大子方阵的边长为:"+solve(A)); 22 } 23 24 private static void print(int[][][] rec, int N) { 25 for (int i = 0; i < N; i++) { 26 for (int j = 0; j < N; j++) { 27 System.out.print(rec[i][j][0] + "," + rec[i][j][1] + " "); 28 } 29 System.out.println(); 30 } 31 } 32 33 static int [][][]rec; 34 /** 35 * 记录每个元素往右和往下有多少个连续的1 36 * @param A 37 */ 38 private static void generateHelpRec(int[][] A) { 39 int N = A.length; 40 rec = new int[N][N][2]; 41 int row = N-1; 42 // 初始化最后一行,因为记录的是往右和往下的,所以必须从最后一行向上初始化,而且从右向左初始化 43 for(int j=N-1;j>=0;j--){ 44 int value = A[row][j]; 45 if (value==1) { 46 if (j==N-1) { // 避免数组越界 47 rec[row][j][0] = 1; // 右边界 48 }else { 49 // A的元素值为1,rec在这个位置的连续1的个数 = 右边位置的连续的1的个数+1 50 rec[row][j][0] = rec[row][j+1][0] + 1; 51 } 52 // 最后一行的下方的1的连续数==1 53 rec[row][j][1] = 1; 54 } 55 } 56 row--; //开始初始化倒数第二行到第一行 57 for(int i = row;i>=0;i--){ 58 for (int j = N-1; j>=0; j--) { 59 int value = A[i][j]; 60 // 利用右边和下边已经生产的数据来推出现在这个位置上右侧和下方有多少个1 61 if(value==1){ 62 if (j==N-1) { 63 rec[i][j][0] = 1;// 右侧连续1的个数 64 }else { 65 rec[i][j][0] = rec[i][j+1][0]+1; 66 } 67 68 rec[i][j][1] = rec[i+1][j][1] + 1; // 向下连续1的个数 69 } 70 } 71 } 72 } 73 74 // O(n³) 75 static int solve(int [][]A){ 76 int N = A.length; 77 int n = N; 78 while(n>0){ 79 for (int i = 0; i < N; i++) { 80 if (i+n>N) break; 81 l3: // 这个语言相当于给第二层for循环命令 下面代码中的continue继续的是第二层的for循环,不是while循环 82 for (int j = 0; j < N; j++) { 83 if (j+n>N) break; 84 if (check(i, j, n)) 85 return n; 86 } 87 } 88 n--; 89 } 90 return 0; 91 } 92 93 // O(1) 94 private static boolean check(int i, int j, int n) { 95 // 左上角那个点往右数的1的数目要大于等于n 96 // 左上角那个点往下数的1的数目要大于等于n 97 // 右上角那个点往下数的1的个数要大于等于n 98 // 左下角那个点往右数的1的个数要大于等于n 99 if (rec[i][j][0]>=n&&rec[i][j][1]>=n&&rec[i][j+n-1][1]>=n&&rec[i+n-1][j][0]>=n) { 100 return true; 101 } 102 return false; 103 } 104 105 106 }
结果:
总结:我们再来计算优化过后的时间复杂度为:生成辅助数组所花时间N2+后面循环遍历时间N3,加起来整个算法的时间复杂度就为O(N3)了,那这个算法的效率就比优化前的算法的效率好太多了。