1. 程式人生 > >[LeetCode] Longest Line of Consecutive One in Matrix 矩陣中最長的連續1

[LeetCode] Longest Line of Consecutive One in Matrix 矩陣中最長的連續1

Given a 01 matrix M, find the longest line of consecutive one in the matrix. The line could be horizontal, vertical, diagonal or anti-diagonal.

Example:

Input:
[[0,1,1,0],
 [0,1,1,0],
 [0,0,0,1]]
Output: 3

Hint: The number of elements in the given matrix will not exceed 10,000.

這道題給了我們一個二維矩陣,讓我們求矩陣中最長的連續1,連續方向任意,可以是水平,豎直,對角線或者逆對角線均可。那麼最直接最暴力的方法就是四個方向分別來統計最長的連續1,其中水平方向和豎直方向都比較容易,就是逐行逐列的掃描,使用一個計數器,如果當前位置是1,則計數器自增1,並且更新結果res,否則計數器清零。對於對角線和逆對角線需要進行些座標轉換,對於一個mxn的矩陣,對角線和逆對角線的排數都是m+n-1個,難點在於我們要確定每一排上的數字的座標,如果i是從0到m+n-1之間遍歷,j是在i到0之間遍歷,那麼對角線的數字的座標就為(i-j, j),逆對角線的座標就為(m-1-i+j, j),這是博主千辛萬苦試出來的T.T,如果能直接記住,效果肯定棒!那麼有了座標轉換,求對角線和逆對角線的連續1也就不是啥難事了,參見程式碼如下:

解法一:

class Solution {
public:
    int longestLine(vector<vector<int>>& M) {
        if (M.empty() || M[0].empty()) return 0;
        int res = 0, m = M.size(), n = M[0].size();
        for (int i = 0; i < m; ++i) { // Check horizontal
            int cnt = 0;
            for
(int j = 0; j < n; ++j) { if (M[i][j] == 1) res = max(res, ++cnt); else cnt = 0; } } for (int j = 0; j < n; ++j) { int cnt = 0; for (int i = 0; i < m; ++i) { // Check vertical if (M[i][j] == 1
) res = max(res, ++cnt); else cnt = 0; } } for (int i = 0; i < m + n - 1; ++i) { int cnt1 = 0, cnt2 = 0; for (int j = i; j >= 0; --j) { if (i - j < m && j < n) { // Check diagonal if (M[i - j][j] == 1) res = max(res, ++cnt1); else cnt1 = 0; } int t = m - 1 - i + j; if (t >= 0 && t < m && j < n ) { // Check anti-diagonal if(M[t][j] == 1) res = max(res, ++cnt2); else cnt2 = 0; } } } return res; } };

如果上面的解法的座標轉換不好想的話,我們也可以考慮用DP解法來做,我們建立一個三維dp陣列,其中dp[i][j][k]表示從開頭遍歷到數字nums[i][j]為止,第k種情況的連續1的個數,k的值為0,1,2,3,分別對應水平,豎直,對角線和逆對角線這四種情況。之後就是更新dp陣列的過程了,如果如果數字為0的情況直接跳過,然後水平方向就加上前一個的dp值,豎直方向加上上面一個數字的dp值,對角線方向就加上右上方數字的dp值,逆對角線就加上左上方數字的dp值,然後每個值都用來更新結果res,參見程式碼如下:

解法二:

class Solution {
public:
    int longestLine(vector<vector<int>>& M) {
        if (M.empty() || M[0].empty()) return 0;
        int m = M.size(), n = M[0].size(), res = 0;
        vector<vector<vector<int>>> dp(m, vector<vector<int>>(n, vector<int>(4)));
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (M[i][j] == 0) continue;
                for (int k = 0; k < 4; ++k) dp[i][j][k] = 1;
                if (j > 0) dp[i][j][0] += dp[i][j - 1][0]; // horizonal
                if (i > 0) dp[i][j][1] += dp[i - 1][j][1]; // vertical
                if (i > 0 && j < n - 1) dp[i][j][2] += dp[i - 1][j + 1][2]; // diagonal
                if (i > 0 && j > 0) dp[i][j][3] += dp[i - 1][j - 1][3]; // anti-diagonal
                res = max(res, max(dp[i][j][0], dp[i][j][1]));
                res = max(res, max(dp[i][j][2], dp[i][j][3]));
            }
        }
        return res;
    }
};

下面我們來優化空間複雜度,用一種類似於DFS的思路來解決問題,我們在遍歷到為1的點時,對其水平方向,豎直方向,對角線方向和逆對角線方向分別不停遍歷,直到越界或者遇到為0的數字,同時用計數器來累計1的個數,這樣就可以用來更新結果res了,就不用把每個中間結果都儲存下來了,參見程式碼如下:

解法三:

class Solution {
public:
    int longestLine(vector<vector<int>>& M) {
        if (M.empty() || M[0].empty()) return 0;
        int m = M.size(), n = M[0].size(), res = 0;
        vector<vector<int>> dirs{{1,0},{0,1},{-1,-1},{-1,1}};
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (M[i][j] == 0) continue;
                for (int k = 0; k < 4; ++k) {
                    int cnt = 0, x = i, y = j;
                    while (x >= 0 && x < m && y >= 0 && y < n && M[x][y] == 1) {
                        x += dirs[k][0];
                        y += dirs[k][1];
                        ++cnt;
                    }
                    res = max(res, cnt);
                }
            }
        }
        return res;
    }
};

參考資料: