題目網址:https://leetcode.cn/problems/number-of-islands/
題意: 給一 m x n
的二維矩陣 grid
, 其中 '1'
代表陸地, 而 '0'
代表水, 返回島嶼的個數。
每座島嶼只能由水平 or 垂直方向的相鄰陸地連接而成。
Solution 1:
想法:利用 DFS, 若 grid[row][col] == 1
, 則以其為起始 node 開始進行 DFS。在 DFS 的過程中. 每個搜索到的 1
都會被重新標記為 0
, 最後整個地圖都會變成水
class Solution {public : int numIslands (vector<vector<char >>& grid) { m = grid.size (), n = grid[0 ].size (); int res = 0 ; for (int i = 0 ; i < m; ++i) { for (int j = 0 ; j < n; ++j) { if (grid[i][j] == '1' ) { dfs (grid, i, j); ++res; } } } return res; } private : int m, n; vector<pair<int , int >> dirs = { {0 , 1 }, {0 , -1 }, {1 , 0 }, {-1 , 0 } }; void dfs (vector<vector<char >>& grid, int i, int j) { if (outOfBound (i, j) || grid[i][j] == '0' ) { return ; } grid[i][j] = '0' ; for (const auto & [dirY, dirX] : dirs) { dfs (grid, i + dirY, j + dirX); } } bool outOfBound (int i, int j) { if (i < 0 || i == m || j < 0 || j == n) { return true ; } return false ; } };
time: $O(m \cdot n)$ ➔ for loop
space: $O(m \cdot n)$ ➔ 取決於遞迴深度, 最大遞迴深度為 $mn$(worse case:全部都是陸地)
Solution 2:
想法:利用 BFS, 概念類似 Solution 1, 拜訪後一樣要設為 0
。若在 q.pop()
下面放 grid[row][col] = '0'
, 而不是在將 {r, c}
push 到 q
時將 grid[r][c]
設為 0
, 這樣會使 node 被重複拜訪, 導致 TLE
e.g. grid = [[1, 1], [1, 1]]
若是在 q.pop()
下面放 grid[row][col] = '0'
, 則 grid[1][1]
會被 grid[0][1]
和 grid[1][0]
重複拜訪(因為 grid[1][1]
在下一輪的 q.pop()
後才被設為 0)
若是在將 {r, c}
push 到 q
時將 grid[r][c]
設為 0
, 則 grid[1][1]
只會被 grid[1][0]
拜訪。因為在 grid[0][1]
的 for loop 中 if (grid[1][1] == '1')
不會成立, 因為 grid[1][1]
已經在 grid[1][0]
的 for loop 中被設成 0
typedef pair<int , int > pii;class Solution {public : int numIslands (vector<vector<char >>& grid) { m = grid.size (), n = grid[0 ].size (); int res = 0 ; for (int i = 0 ; i < m; ++i) { for (int j = 0 ; j < n; ++j) { if (grid[i][j] == '1' ) { bfs (grid, i, j); ++res; } } } return res; } private : int m, n; queue<pii> q; vector<pii> dirs = { {0 , 1 }, {0 , -1 }, {1 , 0 }, {-1 , 0 } }; void bfs (vector<vector<char >>& grid, const int i, const int j) { q.emplace (pii{i, j}); while (!q.empty ()) { for (int k = q.size (); k > 0 ; --k) { const auto [row, col] = q.front (); q.pop (); for (const auto & [dirY, dirX] : dirs) { const int r = row + dirY, c = col + dirX; if (!outOfBound (r, c) && grid[r][c] == '1' ) { grid[r][c] = '0' ; q.emplace (pii{r, c}); } } } } } bool outOfBound (const int i, const int j) { if (i < 0 || i == m || j < 0 || j == n) { return true ; } return false ; } };
time: $O(m \cdot n)$ ➔ for loop
space: $O(min(m, n))$ ➔ worse case:全部都是陸地, 則 q
的長度最多為 min(m, n)
, 也就是圖中線的最大長度
Solution 3:
想法:利用 Union Find + Path Compression, 將所有的連在一起的 1
的位置歸到一個 root
, 最後有多少個 root
就有多少個島嶼。值得注意的是, 與 DFS 或者 BFS 的方法不同的是, 不需要搜索四個方向, 只需要看兩個方向(右、下)即可
class Solution {public : int numIslands (vector<vector<char >>& grid) { m = grid.size (), n = grid[0 ].size (); parents.resize (m * n, 0 ); ranks.resize (m * n, 1 ); for (int i = 0 ; i < m * n; ++i) { parents[i] = i; } int res = 0 ; for (int i = 0 ; i < m; ++i) { for (int j = 0 ; j < n; ++j) { if (grid[i][j] == '1' ) { ++res; for (const auto & [dirY, dirX] : dirs) { const int r = i + dirY, c = j + dirX; if (!outOfBound (r, c) && grid[r][c] == '1' ) { union_ (i * n + j, r * n + c, res); } } } } } return res; } private : int m, n; vector<int > parents, ranks; vector<pair<int , int >> dirs = { {0 , 1 }, {1 , 0 } }; void union_ (const int n1, const int n2, int & res) { const int r1 = find (n1); const int r2 = find (n2); if (r1 == r2) { return ; } if (ranks[r1] > ranks[r2]) { parents[r2] = r1; } else if (ranks[r1] < ranks[r2]) { parents[r1] = r2; } else { parents[r2] = r1; ++ranks[r1]; } --res; } int find (int i) { while (i != parents[i]) { parents[i] = parents[parents[i]]; i = parents[i]; } return i; } bool outOfBound (const int i, const int j) { if (i < 0 || i == m || j < 0 || j == n) { return true ; } return false ; } };
time: $O(m \cdot n)$ ➔ for loop
space: $O(m \cdot n)$ ➔ parent
, ranks