問題
地図をgrid を"1" (陸) か "0" (海) です。
- 斜めに
隣り合うだけの 陸は、 つながっていない ものとします - 地図の
外側は、 すべて 海だと考えます - 行の
数と 列の 数は、 どちらも 1 以上 300 以下です。 どの 行も 同じ長さです - 関数の
中で gridを書き換えてかまいません
入力と出力の例
例 1
左上の 3 マス、
入力
grid = [["1","1","0","0","1"],
["1","0","0","0","1"],
["0","0","1","0","0"]]出力
3例 2
斜めに
入力
grid = [["1","0","1"],
["0","1","0"],
["1","0","1"]]出力
5例 3
真ん中に
入力
grid = [["1","1","1"],
["1","0","1"],
["1","1","1"]]出力
1面接での聞かれ方
面接では、
- 「0 と 1 の
グリッドが あります。 1 のかたまりは いく つありますか」 - 書き終えた
あとに 「再帰で 書きましたね。 グリッドが 1000 × 1000 で 全部陸だったら どうなりますか」と、 深さの 限界を 聞かれます - 「入力を
書き換えて よいですか」 「書き換えられないなら どうしますか」と、 見た マスの 印の 付け方を 聞かれる こともあります
斜めを
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
地図を、
段 2
同じ島を 2 回数えないために、"0" に
段 3
広げ方は、"0" に
段 4
ほかの
解答
Python
Python
from collections import deque
STEPS = ((1, 0), (-1, 0),
(0, 1), (0, -1))
def sink(g, r, c):
h, w = len(g), len(g[0])
g[r][c] = "0"
q = deque([(r, c)])
while q:
y, x = q.popleft()
for dy, dx in STEPS:
a = y + dy
b = x + dx
if not 0 <= a < h:
continue
if not 0 <= b < w:
continue
if g[a][b] == "1":
g[a][b] = "0"
q.append((a, b))
def count_islands(grid):
count = 0
w = len(grid[0])
for r in range(len(grid)):
row = grid[r]
for c in range(w):
if row[c] == "1":
count += 1
sink(grid, r, c)
return countJavaScript
JavaScript
const STEPS = [[1, 0], [-1, 0],
[0, 1], [0, -1]];
function sink(g, r, c) {
g[r][c] = "0";
const q = [[r, c]];
let i = 0;
while (i < q.length) {
const [y, x] = q[i++];
for (const d of STEPS) {
const a = y + d[0];
const b = x + d[1];
// 外側は undefined になる
if (g[a]?.[b] !== "1") {
continue;
}
g[a][b] = "0";
q.push([a, b]);
}
}
}
function countIslands(grid) {
let count = 0;
grid.forEach((row, r) => {
row.forEach((v, c) => {
if (v !== "1") return;
count++;
sink(grid, r, c);
});
});
return count;
}Go
Go
var steps = [][2]int{
{1, 0}, {-1, 0},
{0, 1}, {0, -1},
}
func sink(
g [][]string, r, c int,
) {
h, w := len(g), len(g[0])
g[r][c] = "0"
q := [][2]int{{r, c}}
for len(q) > 0 {
p := q[0]
q = q[1:]
for _, d := range steps {
a := p[0] + d[0]
b := p[1] + d[1]
if a < 0 || a >= h {
continue
}
if b < 0 || b >= w {
continue
}
if g[a][b] != "1" {
continue
}
g[a][b] = "0"
next := [2]int{a, b}
q = append(q, next)
}
}
}
func countIslands(
grid [][]string,
) int {
count := 0
for r := range grid {
for c := range grid[r] {
if grid[r][c] == "1" {
count++
sink(grid, r, c)
}
}
}
return count
}JavaScript の q.shift() はi をg[a]?.[b] は、undefined にq[1:] で
計算量
- 時間は O(mn) です (m は
行の 数、 n は 列の 数)。 外側の 2 重の 繰り返しで 全マスを 1 回ずつ 見て、 どの マスも キューに 入るのは 多くて 1 回、 入った マスは 上下左右の 4 つを 調べるだけです - 使う
記憶は O(mn) です。 キューに 同時に 入る マスは、 最悪で 地図の 大きさに 比例します (幅優先なら 実際には O(min(m, n)) ほどで 済むことが 多いですが、 上限としては O(mn) と 答えます) - 入力を
書き換えられない ときは、 同じ 大きさの 「見た」の 表を 別に 持つので、 記憶は O(mn) のままです
よく ある間違い
- キューから
取り出した ときに 印を付ける。 入れる ときに "0"にしないと、 同じ マスが 別の 隣から 何度も キューに 入ります。 答えは 合っても、 全部陸の 地図では キューが 何倍にも 膨れ、 時間切れになります - 再帰の
深さを 考えない。 再帰の 深さ優先探索は 小さな 例では 通りますが、 Python では 1000 段ほどで RecursionErrorになります。 面接で 再帰を 書いたなら、 「深い陸では 上限に かかるので、 本番なら スタックか キューの 繰り返しに 替えます」と 自分から 言えると 評価が 上がります - 数字の 1 と
文字の "1" を 取り違える。 マスは 文字なので、 grid[r][c] == 1と比べると 一度も 一致せず、 答えが いつも 0 になります - 端の
確かめを忘れる。 上の 行を 見る ときの 行の 番号が -1 に なると、 Python は 最後の 行を 読んでしまい、 エラーに ならないまま 島が つながって数が 減ります。 範囲の 確かめは、 読む前に 必ず置きます - 斜めまで広げる。 8 方向に
広げると、 例 2 が 5 ではなく 1 に なります。 方向の 表を 4 つに しているかを 見直します
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「地図を
グラフと 見て、 陸のかたまりを 数えます。 左上から 見ていき、 陸に 出会ったら 島を 1 つ 数えて、 そこから 上下左右に 広がる 陸を 全部海に 塗りつぶします。 塗りつぶしは 再帰でも 書けますが、 300 × 300 が 全部陸だと 深さが 9 万段近くに なるので、 キューを 使った 幅優先で 書きます。 全マスを 定数回ずつしか 見ないので、 時間は O(mn)、 記憶は キューの 分の O(mn) です。」
書き終えたら、
「例 2 の
斜めの 地図で たどります。 左上の 陸で 島を 1 つ 数えて 塗りますが、 上下左右は どれも 海なので、 塗るのは その マスだけです。 真ん中も、 四隅の 残り 3 つも 同じなので、 島は 5 つです。 斜めには 広げないので、 ここで 1 つに まとまる ことは ありません。」
