题目
在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
示例:
输入:
1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0
输出: 4
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/maximal-square
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析
有两种办法。
一是最直观的暴力轮询。以每一个顶点作位正方形左上角的顶点,依次遍历n*n正方形所需的所有的1,当遇到0时判断是否更新max的记录,然后continue。可以剪枝,当行或者列剩余方格不足当前max值时可以剪枝。
二是动态规划,我自己是没有找到递推式的。
看了题解区了解到,对于0的方格直接置0,对于为1的格子可以更新为:
dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1
我觉得这个式子一般人是有点难找到的。
以下用一个例子具体说明这个式子的正确性。原始矩阵如下。
0 1 1 1 0
1 1 1 1 0
0 1 1 1 1
0 1 1 1 1
0 0 1 1 1
对应的 dp矩阵 值如下。
0 1 1 1 0
1 1 2 2 0
0 1 2 3 1
0 1 2 3 2
0 0 1 2 3
下图也给出了计算dp值的过程。
观察上图,对于会有更新的一个数字(比如从2到3),它的左方、上方、右方必须同时为2的时候才能更新为3,否则就会更新为最小的那个数值加一。这个可以分别在三个数字最小的时候分类讨论得到。
说实话,我自己也没有想到这个动态规划的递推表达式,所以我其实是对每一个点都需要轮询搜索判断一下它左边和上边是不是都是1再进行参数更新。这样子代码会长不少。
依照惯例,先上暴力再上自己家的动规再上别人家的动规。
暴力代码
void UpdateMax(int *max, int sum){
if (sum > *max) {
*max = sum;
}
}
int MaxRow(char** matrix, int row, int col, int max){
int total = 0;
for (int j = col; j < max; j++) {
if (matrix[row][j] == '1') {
total++;
} else {
break;
}
}
return total;
}
int MaxCol(char** matrix, int row, int col, int max){
int total = 0;
for (int i = row; i < max; i++) {
if (matrix[i][col] == '1') {
total++;
} else {
break;
}
}
return total;
}
bool CheckZero(char** matrix, int row, int col, int maxEdge){
for (int i = row; i < row + maxEdge; i++) {
for (int j = col; j < col + maxEdge; j++) {
if (matrix[i][j] == '0') {
return true;
}
}
}
return false;
}
bool CheckParam(char** matrix, int matrixSize, int* matrixColSize)
{
if (matrixSize == 0) {
return true;
}
return false;
}
int maximalSquare(char** matrix, int matrixSize, int* matrixColSize)
{
int row = matrixSize;
int col = 0;
int maxRow = 0;
int maxCol = 0;
int maxEdge = 0;
int sum = 0;
int res = 0;
if (CheckParam(matrix, matrixSize, matrixColSize)) {
return 0;
}
col = *matrixColSize;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (matrix[i][j] == '1') {
maxRow = MaxRow(matrix, i, j, col);
maxCol = MaxCol(matrix, i, j, row);
maxEdge = fmin(maxRow, maxCol);
while (CheckZero(matrix, i, j, maxEdge)) {
maxEdge--;
}
sum = maxEdge * maxEdge;
UpdateMax(&res, sum);
}
}
}
return res;
}
暴力轮询结果
自己家的动规代码
int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){
if(0 == matrixSize){
return 0;
}
int maxlen = 0;
int row = *matrixColSize;
char* tmp;
int** pstbuf = malloc(sizeof(int*)* matrixSize);
for(int i = 0; i < matrixSize; i++){
pstbuf[i] = malloc(sizeof(int)* row);
}
tmp = matrix[0];
for(int i = 0; i < row; i++){
if(tmp[i] == '1'){
pstbuf[0][i] = 1;
maxlen = 1;
} else {
pstbuf[0][i] = 0;
}
}
for(int i = 1; i < matrixSize; i++){
tmp = matrix[i];
if(tmp[0] == '1'){
pstbuf[i][0] = 1;
maxlen = 1;
}else{
pstbuf[i][0] = 0;
}
}
//上面部分代码处理边界,单独拿出来作为后面部分的“哨兵”,防止溢出
for(int i = 1; i < matrixSize; i++){
tmp = matrix[i];
for(int j = 1; j < row; j++){
if(tmp[j] == '0'){
pstbuf[i][j] = 0;
}
else if (pstbuf[i-1][j-1] == 0){
pstbuf[i][j] = 1;
if(maxlen < pstbuf[i][j]){
maxlen = pstbuf[i][j];
}
} else {
int tmplen = pstbuf[i-1][j-1];
int a = tmplen;
int b = tmplen;
//a,b分别用于记录左侧和上方能延申的最大的有效距离
bool bflag = true;
for(int m = 1; m <=tmplen; m++){//从近处逐渐探测,第一个0处break,然后把flag置为flase
if(matrix[i][j-m]=='0'){
bflag = false;
a = m - 1 ;
break;
}
}
for(int m = 1; m <= tmplen; m++){
if(matrix[i-m][j] == '0'){ //从近处逐渐探测,第一个0处break,然后把flag置为flase
bflag = false;
b = m -1;
break;
}
}
if( bflag == true){ //当左方和上方都满足是1时扩展正方形
pstbuf[i][j] = pstbuf[i-1][j-1] + 1;
} else { //否则选择一个左方或者上方较小的
pstbuf[i][j] = (a < b ? a : b) + 1;
}
if(maxlen < pstbuf[i][j]){
maxlen = pstbuf[i][j];
}
}
}
}
return maxlen*maxlen;
}
运行结果
还真别说,虽然没有用递推表达式,但是运行效率已经相当不错了。
采用递推表达式的动态规划代码
评论区里头复制的,有效代码就短短几行……有递推式的人就是不一样。
#define MIN(a,b,c) ((a)<(b)?( (a)<(c)?(a):(c) ):( (b)<(c)?(b):(c) ))
int maximalSquare(char** matrix, int matrixSize, int* matrixColSize){
if(matrixSize == 0) return 0;
int j,k,ms = matrix[0][0];
for(j=0;j<matrixSize;j++)
for(k=0;k<matrixColSize[j];k++){
if(matrix[j][k]=='1' && j>0 && k>0) matrix[j][k] = MIN(matrix[j-1][k],matrix[j][k-1],matrix[j-1][k-1]) + 1;
if(matrix[j][k] > ms) ms = matrix[j][k];
}
return (ms-'0')*(ms-'0');
}
作者:chao-ren-er-hao
链接:https://leetcode-cn.com/problems/maximal-square/solution/c-yi-ban-jie-fa-dong-tai-gui-hua-by-chao-ren-er-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
不过不知道为什么,运行的速度好像慢上不少,真是奇怪。我运行了好几次,甚至会有几次跑出24ms、28ms的成绩……我又去跑了跑上面长的那段动态规划代码,都是20ms以内。
只能总结为:谁说没有递推式就写不出好的动态规划?因为自己的代码,再丑也得夸不是吗~