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 1
s contained in the grid? Return the order of the plus sign. If there is none, return 0.
An "axis-aligned plus sign of 1
s of order k" has some center grid[x][y] = 1
along with 4 arms of length k-1
going up, down, left, and right, and made of 1
s. This is demonstrated in the diagrams below. Note that there could be 0
s or 1
s 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:
N
will be an integer in the range[1, 500]
.mines
will have length at most5000
.mines[i]
will be length 2 and consist of integers in the range[0, N-1]
.- (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/