[LeetCode] Largest Plus Sign 最大的加型符號


 

In a 2D grid from (0, 0) to (N-1, N-1), every cell contains a 1, except those cells in the given list mines which are 0. What is the largest axis-aligned plus sign of 1s contained in the grid? Return the order of the plus sign. If there is none, return 0.

An "axis-aligned plus sign of 1s of order k" has some center grid[x][y] = 1 along with 4 arms of length k-1going up, down, left, and right, and made of 1s. This is demonstrated in the diagrams below. Note that there could be 0s or 1s beyond the arms of the plus sign, only the relevant area of the plus sign is checked for 1s.

 

Examples of Axis-Aligned Plus Signs of Order k:

Order 1:
000
010
000

Order 2:
00000
00100
01110
00100
00000

Order 3:
0000000
0001000
0001000
0111110
0001000
0001000
0000000

 

Example 1:

Input: N = 5, mines = [[4, 2]]
Output: 2
Explanation:
11111
11111
11111
11111
11011
In the above grid, the largest plus sign can only be order 2.  One of them is marked in bold.

 

Example 2:

Input: N = 2, mines = []
Output: 1
Explanation:
There is no plus sign of order 2, but there is of order 1.

 

Example 3:

Input: N = 1, mines = [[0, 0]]
Output: 0
Explanation:
There is no plus sign, so return 0.

 

Note:

  1. N will be an integer in the range [1, 500].
  2. mines will have length at most 5000.
  3. mines[i] will be length 2 and consist of integers in the range [0, N-1].
  4. (Additionally, programs submitted in C, C++, or C# will be judged with a slightly smaller time limit.)

 

這道題給了我們一個數字N,表示一個NxN的二位數字,初始化均為1,又給了一個mines數組,里面是一些坐標,表示數組中這些位置都為0,然后讓我們找最大的加型符號。所謂的加型符號是有數字1組成的一個十字型的加號,題目中也給出了長度分別為1,2,3的加型符號的樣子。好,理解了題意以后,我們來想想該如何破題。首先,最簡單的就是考慮暴力搜索啦,以每個1為中心,向四個方向分別去找,只要任何一個方向遇到了0就停止,然后更新結果res。令博主感到驚訝的是,此題的OJ居然允許Brute Force的解法通過,還是比較大度的,參見代碼如下:

 

解法一:

class Solution {
public:
    int orderOfLargestPlusSign(int N, vector<vector<int>>& mines) {
        int res = 0;
        vector<vector<int>> mat(N, vector<int>(N, 1));
        for (auto mine : mines) mat[mine[0]][mine[1]] = 0;
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < N; ++j) {
                int k = 0;
                while (canExpand(mat, N, i, j, k)) ++k;
                res = max(res, k);
            }
        }
        return res;
    }
    bool canExpand(vector<vector<int>>& mat, int N, int x, int y, int k) {
        if (x - k < 0 || y - k < 0 || x + k >= N || y + k >= N) return false;
        return mat[x - k][y] && mat[x][y + k] && mat[x + k][y] && mat[x][y - k];
    }
};

 

如果我們只想出暴力搜索的解法,就不再管這道題了的話,那在面試的時候就比較懸了。畢竟立方級的時間復雜度實在是太高了,我們必須要進行優化。暴力搜索的時間復雜度之所以高的原因是因為對於每一個1都要遍歷其上下左右四個方向,有大量的重復計算,我們為了提高效率,可以對於每一個點,都計算好其上下左右連續1的個數。博主最先用的方法是建立四個方向的dp數組,dp[i][j]表示 (i, j) 位置上該特定方向連續1的個數,那么就需要4個二維dp數組,舉個栗子,比如:

原數組:

1  0  1  0
1  1  1  1
1  0  1  1

那么我們建立left數組是當前及其左邊連續1的個數,如下所示:

1  0  1  0
1  2  3  4
1  0  1  2

right數組是當前及其右邊連續1的個數,如下所示:

1  0  1  0
4  3  2  1
1  0  2  1

up數組是當前及其上邊連續1的個數,如下所示:

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

down數組是當前及其下邊連續1的個數,如下所示:

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

我們需要做的是在這四個dp數組中的相同位置的四個值中取最小的一個,然后在所有的這些去除的最小值中選最大一個返回即可。為了節省空間,我們不用四個二維dp數組,而只用一個就可以了,因為對於每一個特定位置,我們只需要保留較小值,所以在更新的時候,只需要跟原來值相比取較小值即可。在計算down數組的時候,我們就可以直接更新結果res了,因為四個值都已經計算過了,我們就不用再重新在外面開for循環了,參見代碼如下:

 

解法二:

class Solution {
public:
    int orderOfLargestPlusSign(int N, vector<vector<int>>& mines) {
        int res = 0, cnt = 0;
        vector<vector<int>> dp(N, vector<int>(N, 0));
        unordered_set<int> s;
        for (auto mine : mines) s.insert(mine[0] * N + mine[1]);
        for (int j = 0; j < N; ++j) {
            cnt = 0;
            for (int i = 0; i < N; ++i) { // up
                cnt = s.count(i * N + j) ? 0 : cnt + 1;
                dp[i][j] = cnt;
            }
            cnt = 0;
            for (int i = N - 1; i >= 0; --i) { // down
                cnt = s.count(i * N + j) ? 0 : cnt + 1;
                dp[i][j] = min(dp[i][j], cnt);
            }
        }
        for (int i = 0; i < N; ++i) {
            cnt = 0;
            for (int j = 0; j < N; ++j) { // left
                cnt = s.count(i * N + j) ? 0 : cnt + 1;
                dp[i][j] = min(dp[i][j], cnt);
            }
            cnt = 0;
            for (int j = N - 1; j >= 0; --j) { // right
                cnt = s.count(i * N + j) ? 0 : cnt + 1;
                dp[i][j] = min(dp[i][j], cnt);
                res = max(res, dp[i][j]);
            }
        }
        return res;
    }
};

 

我們可以進一步的壓縮代碼,使其更加簡潔,我們發現其實只要分別用四個變量l,r,u,d來表示四個方向連續1的個數,既可以將for循環糅合在一起。注意里面內嵌的for循環其實是兩個for循環,由j和k分別控制,那么只要弄清i,j,k坐標的位置,就可以同時更新四個方向的dp值了,最后dp數組更新好了之后,我們再秀一波,只用一個for循環來遍歷二維數組,其實就是把二維坐標壓縮成了一個數字,再解壓縮,參見代碼如下:

 

解法三:

class Solution {
public:
    int orderOfLargestPlusSign(int N, vector<vector<int>>& mines) {
        int res = 0;
        vector<vector<int>> dp(N, vector<int>(N, N));
        for (auto mine : mines) dp[mine[0]][mine[1]] = 0;
        for (int i = 0; i < N; ++i) {
            int l = 0, r = 0, u = 0, d = 0;
            for (int j = 0, k = N - 1; j < N; ++j, --k) {
                dp[i][j] = min(dp[i][j], l = (dp[i][j] ? l + 1 : 0));
                dp[j][i] = min(dp[j][i], u = (dp[j][i] ? u + 1 : 0));
                dp[i][k] = min(dp[i][k], r = (dp[i][k] ? r + 1 : 0));
                dp[k][i] = min(dp[k][i], d = (dp[k][i] ? d + 1 : 0));
            }
        }
        for (int k = 0; k < N * N; ++k) res = max(res, dp[k / N][k % N]);
        return res;
    }
};

 

類似題目:

Cheapest Flights Within K Stops

Minimum Swaps To Make Sequences Increasing

Soup Servings

 

參考資料:

https://leetcode.com/problems/largest-plus-sign/solution/

https://leetcode.com/problems/largest-plus-sign/discuss/113314/JavaC++Python-O(N2)-solution-using-only-one-grid-matrix

https://leetcode.com/problems/largest-plus-sign/discuss/113350/C++-simple-brute-force-easy-to-understand-with-detailed-explanation

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM