島嶼的最大面積
題目
給定一個包含了一些 0
和 1
的非空二維數組 grid
。
一個 島嶼 是由一些相鄰的 1
(代表土地) 構成的組合,這裏的「相鄰」要求兩個 1
必須在水平或者豎直方向上相鄰。你可以假設 grid
的四個邊緣都被 0
(代表水)包圍着。
找到給定的二維數組中最大的島嶼面積。(如果沒有島嶼,則返回面積爲 0
。)
示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
對於上面這個給定矩陣應返回 6
。注意答案不應該是 11
,因爲島嶼只能包含水平或垂直的四個方向的 1
。
示例 2:
[[0,0,0,0,0,0,0,0]]
對於上面這個給定的矩陣, 返回 0
。
函數原型
C的函數原型:
int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize){}
邊界判斷
int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize){
if( grid == NULL || gridSize == 0 || gridColSize == NULL || *gridColSize == 0 )
return 0;
}
算法設計:求最大的聯通分量個數
思路:用 DFS or BFS 求聯通分量的個數,最大的那個就是島嶼的最大面積。
-
四聯通探索與之相連的每一個土地(以及與這些土地相連的土地),那麼探索過的土地總數將是該連通形狀的面積。
-
爲了確保不會多次訪問同一土地,我們每次經過一塊土地時,將這塊土地的值置爲
0
。 -
一般我們會用
visited[]
記錄,但這裏我們可以直接利用grid[]
,初始陸地是1
,遍歷如果見到陸地,就dfs
,在dfs
過程中,每遍歷到一個陸地,就設置爲0
,這樣grid[]
爲1
的那些點就是要繼續遍歷的點。
int dfs(int** grid, int gridSize, int* gridColSize, int i, int j) {
if (i < 0 || j < 0 || i == gridSize || j == *gridColSize || grid[i][j] == 0) // 遞歸結束條件
return 0;
grid[i][j] = 0;
int ans = 1;
int x[4] = {0, -1, 0, 1};
int y[4] = {-1, 0, 1, 0};
// 四聯通,如 (x[0], y[0]) -> (0, -1) -> 南,按次序依次是:南、西、北、東。
for (int k = 0; k < 4; ++k) { // 四個方向都遍歷一次
ans += dfs(grid, gridSize, gridColSize, i + x[k], j + y[k]);
}
return ans;
}
int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize){
if( grid == NULL || gridSize == 0 || gridColSize == NULL || *gridColSize == 0 )
return 0;
int ans = 0;
for (int i = 0; i < gridSize; ++i) {
for (int j = 0; j < *gridColSize; ++j) {
int t = dfs(grid, gridSize, gridColSize, i, j);
ans = (ans > t ? ans : t); // dfs() 返回的是聯通分量的個數,這裏取最大值
}
}
return ans;
}
最大的聯通分量個數的複雜度:
- 時間複雜度:
- 空間複雜度:
算法設計:Flood Fill算法
Flood Fill:從某一點開始,按照某種邏輯遍歷不斷查找鄰點。
-
想象一個二維數組,從左上角
(0,0)
開始遍歷,直到遍歷到右下角(n-1, n-1)
。 -
按照某種邏輯遍歷,可以選
DFS
、BFS
等,這裏採用DFS
。 -
如果是訪問過的鄰點,就染色,代表已經訪問過來。
-
一般我們會用
visited[]
記錄,但這裏我們可以直接利用grid[]
,初始陸地是1
,遍歷如果見到陸地,就dfs
,在dfs
過程中,每遍歷到一個陸地,就設置爲0
,這樣grid[]
爲1
的那些點就是要繼續遍歷的點。
int dfs(int** grid, int gridSize, int* gridColSize, int x, int y) {
if (x < 0 || y < 0 || x == gridSize || y == *gridColSize || grid[x][y] == 0) // 遞歸結束條件:出界判斷。
return 0;
grid[x][y] = 0; // 表示已訪問
int ans = 1;
// 一點點可優化的地方,對數組進行索引也會產生性能開銷,可以省了 `x[]`、`y[]` 。
ans += dfs(grid, gridSize, gridColSize, x - 1, y); // 西
ans += dfs(grid, gridSize, gridColSize, x, y + 1); // 北
ans += dfs(grid, gridSize, gridColSize, x + 1, y); // 東
ans += dfs(grid, gridSize, gridColSize, x, y - 1); // 南
return ans;
}
int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize){
if( grid == NULL || gridSize == 0 || gridColSize == NULL || *gridColSize == 0 )
return 0;
int ans = 0;
for (int i = 0; i < gridSize; ++i) {
for (int j = 0; j < *gridColSize; ++j) {
int t = dfs(grid, gridSize, gridColSize, i, j);
ans = (ans > t ? ans : t); // dfs() 返回的是聯通分量的個數,這裏取最大值
}
}
return ans;
}
爲了可讀性,可以優化 dfs()
的接口參數。
// 引入一個結構體,減少遞歸接口的參數
struct _graph{
int** _grid;
int* _gridColSize;
int _gridSize;
} g;
int dfs(int x, int y){
if (x < 0 || y < 0 || x == g._gridSize || y == (*g._gridColSize) || g._grid[x][y] == 0) return 0;
g._grid[x][y] = 0; // 表示已訪問
int ans = 1;
ans += dfs(x - 1, y);
ans += dfs(x, y + 1);
ans += dfs(x + 1, y);
ans += dfs(x, y - 1);
return ans;
}
int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize){
if( grid == NULL || gridSize == 0 || gridColSize == NULL || *gridColSize == 0 )
return 0;
/* 賦值,可減少遞歸參數 */
g._grid = grid;
g._gridColSize = gridColSize;
g._gridSize = gridSize;
int ans = 0;
for (int i = 0; i < gridSize; ++i) {
for (int j = 0; j < *gridColSize; ++j) {
int t = dfs(i, j);
ans = (ans > t ? ans : t); // dfs() 返回的是聯通分量的個數,這裏取最大值
}
}
return ans;
}
Flood Fill算法的複雜度:
- 時間複雜度:
- 空間複雜度:
算法設計:並查集
Floodfill 算法的本質是,在一個二維數組中,求解連通分量的問題。實際上,這個問題,也可以非常容易的,使用並查集解決。