【算法】最大正方形问题


 虽然棋棋写的都是浅显的东西,不能同那些大佬相提并论,但始终要加油,加油,加加油,毕竟最初的目的是为了自己而记录的。

 记一次动态规划算法——最大正方形问题。

题目描述

给定一个由0和1组成的2维矩阵,返回该矩阵中最大的由1组成的正方形的面积
测试案例1:
输入:
{{'1','0','1','0','0'},
{'1','0','1','1','1'},
{'1','1','1','1','1'},
{'1','0','0','1','0'}}
输出:
4
测试案例2:
输入:
{{'1','1','1','0','0'},
{'1','1','1','1','1'},
{'1','1','1','1','1'},
{'1','0','0','1','0'},
{'1','0','0','1','0'}}
测试案例3:
输入:
{{'1','1','1','1'},
 {'0','1','0','1'}}
输出:
1
测试案例4:
输入:
{{'1','0','1','1','1','1','1','0','0','0','1','0','0','0'},
{'1','0','1','1','1','0','0','0','0','0','0','0','0','0'},
{'1','0','1','0','1','1','1','0','0','0','1','0','0','0'},
{'1','1','0','1','1','1','1','0','1','0','1','0','1','0'},
{'1','1','1','1','1','1','1','0','0','0','1','0','0','0'},
{'1','0','1','1','1','1','1','1','1','0','1','1','1','0'},
{'1','0','1','1','1','1','1','1','1','1','1','1','1','0'},
{'1','0','1','1','1','1','1','1','1','1','1','1','1','0'},
{'1','1','0','1','1','1','1','1','1','1','1','1','0','0'},
{'1','0','1','1','0','1','1','1','1','1','1','1','1','0'},
{'1','0','1','1','0','1','1','1','1','1','1','1','1','1'},
{'1','0','1','1','0','1','1','1','1','1','1','1','0','0'},
{'1','0','1','1','0','1','1','1','1','1','1','1','0','0'},
{'1','0','1','1','0','1','1','0','1','0','1','1','0','0'},
{'1','0','1','1','0','1','1','0','0','0','1','1','0','0'}}
输出:
49

解题思路:

说明:

黑色:上轮查找后满足条件的点。

黄色:本次查找过程中,满足条件的点。

红色:本次查找过程中,不满足条件的点。

思路1:

暴力破解(效率低,不推荐使用):从左到右,自上到下进行遍历,找到以每个点为正方形右下角的最大正方形边长。不说很多公式推导,直接上图,以某个点为例进行说明,其余的点都可以使用同样的方式。

第1次查找,发现目标点满足要求,继续下一次查找。

第2次查找,边长扩大1,本次需要检验的范围是2×2。发现也全部满足,继续下一次查找。

第3次查找,边长继续扩大1,所以本次的正方形为3×3,咦,有一个不满足条件,那么基于这个点为右下角的最大边长就是2。

思路2:暴力破解着实显得有点浪费资源啊,明明第一次已经校验通过的,为什么扩大后又要再校验一次。所以思路2就是在思路1的基础上,站在前人的经验结论继续深造(即前一次确认有效的就不用再校验了,可以直接拿过来使用)。

设dxy为以(x,y)的右下角的最长正方形边长。

假设目标点dxy的左上角d(x-1)(y-1)的结果为2(当然也可以是其他的值),可以得出dxy的最大值就是d(x-1)(y-1)+1。

第1次查找,目标大小1×1,目标满足。

第2次查找,目标大小2×2,看图中可以很明白可以看出来,此次只要检验平行于x轴的一个点,以及平行于y轴的一个点即可,相较于“暴力破解”减少了2次校验。

第3次查找,目标大小3×3,这次继续校验2个点即可,如果满足,那么dxy=3,如果不满足dxy=2。

 总结:思路2与思路1相比,思路2每次查找只要校验2个单元格,但是思路1每次都要找n*n个单元格(可能有人会说,也利用上次,那也要校验n*n-(n-1)*(n-1)次)。

最后贴上我的代码:

 1     private final char zero='0';
 2     public int getMaxSquare(char[][] matrix, int x, int y, int targetLenth){
 3         if (matrix[x][y] == zero){
 4             return 0;
 5         }
 6         int maxSquareSideX = 1, maxSquareSideY=1;
 7         for (int i=x-targetLenth; i){
 8             if (matrix[i][y] != zero){
 9                 maxSquareSideX++;
10             }
11         }
12         for (int j=y-targetLenth; j){
13             if (matrix[x][j] != zero){
14                 maxSquareSideY++;
15             }
16         }
17         return Math.min(maxSquareSideX,maxSquareSideY);
18     }
19     /**
20      * 最大正方形
21      * @param matrix char字符型二维数组
22      * @return int整型
23      */
24     public int solve (char[][] matrix) {
25         int x=matrix.length;
26         int y=matrix[0].length;
27         int lastValue, maxSide = zero;
28         int dp[][] = new int[x][y];
29         //初始化x轴
30         for (int i=0; i){
31             dp[i][0] = matrix[i][0];
32             maxSide = Math.max(maxSide, dp[i][0]);
33         }
34         //初始化y轴
35         for (int i=0; i){
36             dp[0][i] = matrix[0][i];
37             maxSide = Math.max(maxSide, dp[0][i]);
38         }
39         //以dp[i][j]作为右下角,值为边长
40         int side;
41         for (int i=1;i){
42             for (int j=1;j){
43                 lastValue = matrix[i-1][j-1];
44                 if (lastValue > zero &&
45                         matrix[i][j] > zero &&
46                         (side=getMaxSquare(matrix, i, j, dp[i-1][j-1]-zero) + zero) > zero){
47                     dp[i][j] = side;
48                     maxSide = Math.max(maxSide, dp[i][j]);
49                 }else {
50                     dp[i][j] = matrix[i][j];
51                 }
52             }
53         }
54         return (maxSide-zero)*(maxSide-zero);
55     }

相关