ここ は質問の説明です。最初に提案された2つのソリューションには、 DFSおよびBFS が含まれます。この質問は、最初の2つのアプローチ、DFSとBFSに言及しています。
読みやすくするために、問題の説明をここに含めました。
_Given a 2d grid map of '1's (land) and '0's (water), count the number of
islands. An island is surrounded by water and is formed by connecting adjacent
lands horizontally or vertically. You may assume all four edges of the grid are
all surrounded by water.
Example 1:
Input:
11110
11010
11000
00000
Output: 1
Example 2:
Input:
11000
11000
00100
00011
Output: 3
_
なぜDFSとBFSの両方の時間の複雑さがO(rows * columns)
なのかがはっきりしません。グリッドが0でいっぱいになっている場合がこれに該当します。各セルをチェックするだけです。しかし、DFSのアプローチは検索に時間を追加しませんか?アクセスしたセルをdfsメソッドで_0
_に変更してマークを付けたとしても、外側の2つのループのため、すべてのセルに再度アクセスします。 dfがO(n))の時間の複雑さを持つ可能性がある場合、行と列の番号が大きい大きなグリッドの場合、時間の複雑さはO(行*列*最大[ rows、cols])?さらに、O(rows * cols * possibleMaxSizeOfQueue)
であるBFSアプローチの場合と同じではありません。ここでpossibleMaxSizeOfQueue
は_max[rows, cols]
_?
_for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
_
DFSのスペースの複雑さはどうですかO(rows*cols)
?再帰ブランチが戻ったときに、呼び出しスタックスペースが解放されたと見なすことは不可能/一般的ですか? BFS O(min(rows, cols))
のスペースの複雑さはどうですか?私の見たところ、グリッドが1だけの場合、キューはすべての要素でいっぱいになる可能性があり、それによってBFSスペースの複雑さのためにO(rows*cols)
が与えられます。
DFSソリューション
_class Solution {
void dfs(char[][] grid, int r, int c) {
int nr = grid.length;
int nc = grid[0].length;
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
return;
}
grid[r][c] = '0';
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
}
_
時間の複雑さ:O(M×N)ここで、Mは行数、Nは列数です。
スペースの複雑さ:グリッドマップがDFSがM×Nの深さで進むランドで満たされている場合の最悪のケースO(M×N)。
BFSソリューション
_class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
grid[r][c] = '0'; // mark as visited
Queue<Integer> neighbors = new LinkedList<>();
neighbors.add(r * nc + c);
while (!neighbors.isEmpty()) {
int id = neighbors.remove();
int row = id / nc;
int col = id % nc;
if (row - 1 >= 0 && grid[row-1][col] == '1') {
neighbors.add((row-1) * nc + col);
grid[row-1][col] = '0';
}
if (row + 1 < nr && grid[row+1][col] == '1') {
neighbors.add((row+1) * nc + col);
grid[row+1][col] = '0';
}
if (col - 1 >= 0 && grid[row][col-1] == '1') {
neighbors.add(row * nc + col-1);
grid[row][col-1] = '0';
}
if (col + 1 < nc && grid[row][col+1] == '1') {
neighbors.add(row * nc + col+1);
grid[row][col+1] = '0';
}
}
}
}
}
return num_islands;
}
}
_
時間の複雑さ:O(M×N)ここで、Mは行数、Nは列数です。
スペースの複雑さ:O(min(M、N))。グリッドがランドで満たされる最悪の場合、キューのサイズはmin(M、N)まで大きくなる可能性があるためです。
DFSの時間の複雑さは、訪問したグラフの頂点およびエッジの総数に比例します。その場合、_N*M
_頂点があり、_4*N*M
_エッジよりわずかに少ないですが、それらの合計はO(N*M)
のままです。
理由:各エッジで各エッジを1回だけ処理するため。再帰呼び出しがすぐに終了する状況は、その呼び出しに費やされた時間を呼び出しサイトで説明できるため、重要ではありません。そして、指定された各Edgeに対して最大で1回の呼び出しがあるため、O(N*M)
になります。
BFSの時間の複雑さは非常に似ています。キューの全体の長さを調べることはないため、キューの最大長はまったく問題になりません。キューは「追加」と「最初に削除」のクエリのみを取得し、キューのサイズに関係なく一定の時間で処理できます。頂点に既にアクセスしたかどうかを確認する必要がある場合は、一定の時間内にアクセスします。
DFSの最悪の場合のスペースの複雑さはTheta(N*M)
です。「蛇のように」迷路をとるだけです。
_......
#####.
......
.#####
......
_
ここで、DFSは停止してスタックの解放を開始する前に、パス全体を通過するように強制されます。ただし、どのような状況でも、スタックには_N*M+1
_を超える要素はありません。
BFSの最悪の場合のスペースの複雑さは確かにO(max(N, M))
ではありません。考慮している場合でもTheta(N*M)
ですシンプルなグリッド。 math.stackexchange.com の例を次に示します。
赤い点でBFSを開始すると、ツリーのすべての葉を含むキューが作成され、その数は_N*M
_に比例します。例の3/4を切り捨てて、左上隅に赤い点を表示することもできます。
あなたが読んだソリューションは、BFSの最悪の場合のメモリ消費に関して間違っているようです。