分类 Algorithm 下的文章

最大正方形


问题

0,1矩阵中最大的全1正方形

例如:

int[][] a = {
    {0, 1, 1, 1, 0},
    {0, 1, 0, 1, 0},
    {0, 1, 1, 1, 0},
    {1, 1, 1, 1, 1},
    {1, 1, 0, 1, 1},
    {1, 1, 1, 1, 1},
    {0, 1, 1, 1, 0},
    {0, 1, 1, 1, 0},
};

答案应该是: 2

解答

朴素的思路

思路上是把一个二维搜索问题变成了一个一维搜索问题
即将二维数组转化为柱状图, 在柱状图上找正方形, 例如上述例子转为柱状图后:

0, 1, 1, 1, 0, 
0, 2, 0, 2, 0, 
0, 3, 1, 3, 0, 
1, 4, 2, 4, 1, 
2, 5, 0, 5, 2, 
3, 6, 1, 6, 3, 
0, 7, 2, 7, 0, 
0, 8, 3, 8, 0, 

具体的找法比较绕口, 不如直接看代码:

private static int findMaxSquare(int[][] array){
    if(array.length == 0) return 0;

    int rows = array.length, cols = array[0].length;
    for(int i = 1; i < rows; i++){
        for(int j = 0; j < cols; j++){
            if(array[i][j] > 0){
                array[i][j] = array[i - 1][j] + 1;
            }
        }
    }

    int maxVal = 0;
    for(int[] row : array){
        for(int i = 0; i < cols - maxVal; i++){
            if(row[i] > maxVal){
                int cnt = 0, maxLen = row[i];
                for(int j = i; j < cols && cnt < maxLen && row[j] > cnt; j++, cnt++){
                    maxLen = Math.min(maxLen, row[j]);
                }
                maxVal = Math.max(maxVal, cnt);
            }
        }
    }
    return maxVal;
}

简单的来说, 就是找到可能的正方形左下角点以后, 向右搜索(cnt < maxLen && row[j] > cnt):

  • 一方面走的步数(cnt)不能大于目前已经走过的所有值的最小值(maxLen)
  • 另外一方面已经走过的值(row[j])必须大于当前走过的步数(cnt)

总而言之, 这是一个最坏情况O(N^3)的方法, 不过总体来说, 其实性能不算太差.

动态规划

如果一个点的它的左边, 左上, 上边至少构成某个长度(n)的正方形的话, 那么这个点应该能构成一个更大的正方形(n+1)
这样说起来可能有点绕...看例子吧:

0, 1, 1, 1, 0, 
0, 1, 0, 1, 0, 
0, 1, 1, 1, 0, 
1, 1, 2, 2, 1, 
1, 2, 0, 1, 2, 
1, 2, 1, 1, 2, 
0, 1, 2, 2, 0, 
0, 1, 2, 3, 0, 

题目中的例子整个状态空间如上图所示, 很显然...状态方程如下是成立的:

$$dp[i][j] = \min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1$$

所以实现下来的话...

private static int findMaxSquare(int[][] array){
    if(array.length == 0) return 0;
    int rows = array.length, cols = array[0].length, maxVal = 0;
    
    int[][] dp = new int[rows][cols];
    for(int i = 0; i < rows; i++){
        if(array[i][0] == 1){
            dp[i][0] = 1;
            maxVal = 1;
        }
    }
    for(int j = 0; j < cols; j++){
        if(array[0][j] == 1){
            dp[0][j] = 1;
            maxVal = 1;
        }
    }

    for(int i = 1; i < rows; i++){
        for(int j = 1; j < cols; j++){
            if(array[i][j] == 1){
                dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
            }
            maxVal = Math.max(maxVal, dp[i][j]);
        }
    }
    return maxVal;
}

总而言之, 这是一个最坏情况O(N^2)的方法, 只用把数组扫描一遍即可Orz

总结

动态规划的主要难点在于想出状态方程...