选自力扣dp中个人比较认为有代表性的一题
这个与背包问题那类不同,结合了二阶矩阵
思路及解题步骤
对于二阶矩阵,我们常用二阶数组去记录
由于输入中 000 代表黑色相似,因此寻找四条边皆为黑色像素的最大子方阵等价于寻找边界全部由 000 组成的最大正方形子矩阵。
我们定义子子矩阵右下方定点为(x,y),最大边长为L,那么四个定点为(x-L+1,y-L+1),(x,y-L+1),(x-L+1,y),(x,y),此时我们要保证的四条边都为0,那么我们可以设left[x][y]表示(x,y)为起点左边连续0的最大数量,up[x][y]同理,rightP,down同理,
如果连续 0 的数目大于等于 L,则构成一条「合法」的边,如果正方形的四条边均「合法」,此时一定可以构成边界全为 0 且边长为 L 的正方形。
所以我们只需求出以(x,y)为起点四个方向上连续0的数目,其实真正要求的就是left和up因为起点为右下方的顶点
如果当前 matrix[x][x]=1,此时四个方向的连续 000 的长度均为 000;
如果当前 matrix[x][x]=0,此时四个方向的连续 000 的最大数目分别等于四个方向上前一个位置的最大数目加 111,计算公式如下:
left[x][y]= left[x][y-1]+1
up[x][y]= up[x-1][y]+1;
可能到这里理解比较抽象,但是总体的思想就是状态转移,起始点不断地变换但是后一个的有时候(1,0判断)是要根据前一个的状态来决定
求出left和up最大值取它们中的较小值为边长,最后在遍历其他的顶点以判断是否能不能构成矩阵
当获取动态数组后,需要遍历整个动态规划数组求最终结果,同样是i,j嵌套循环,因此可以合并。当获取 left up 要保证是正方形,因此 L = min(left[i][j][0],up][j][1]),
当然也要考虑边界border
public static int [] indSquare(int[][] matrix){
int n = matrix.length;
int[][] left = new int[n + 1][n + 1];
int[][] up = new int[n + 1][n + 1];
int r = 0, c = 0, size = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (matrix[i - 1][j - 1] == 0) {
left[i][j] = left[i][j - 1] + 1;
up[i][j] = up[i - 1][j] + 1;
int border = Math.min(left[i][j], up[i][j]);
while (left[i - border + 1][j] < border || up[i][j - border + 1] < border) {
border--;
}
if (border > size) {
r = i - border;
c = j - border;
size = border;
}
}
}
}
return size > 0 ? new int[]{r, c, size} : new int[0];
}
这道题的返回值除了最大黑方阵的边长以外,还包括最大黑方阵的左上角行列下标。可以在动态规划的计算过程中维护最大黑方阵的左上角的行列下标,当一个方阵的右下角行列下标和边长确定时,即可计算得到该方阵的左上角行列下标。计算结束之后,如果最大边长大于 000 则返回最大黑方阵的左上角行列下标和边长,如果最大边长等于 000 则表示没有黑方阵,返回空数组。
由于动态规划的计算顺序为从上到下和从左到右,因此如果有多个最大黑方阵,返回的最大黑方阵一定满足左上角行下标最小,当存在左上角行下标并列最小的情况时时满足左上角列下标最小。