[leetcode日記]221.最大正方形

題目

在一個由 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以內。

只能總結爲:誰說沒有遞推式就寫不出好的動態規劃?因爲自己的代碼,再醜也得誇不是嗎~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章