盤の 端から たどって 囲まれた 空き地だけを埋める
土と
海外で定番の問題リスト 4 本には入っていません
問題
畑のboard で"X" (土) か "O" (水) です。
畑の
- 盤の
端に つながっている 水たまりは、 そのまま残します - 斜めに
しか 隣り合っていない 水どう しは、 つながっていない ものとします - 行の
数と 列の 数は、 どちらも 1 以上 50 以下です。 どの 行も 同じ長さです
入力と出力の例
例 1
まん中の 3 マスの
例 2
右下の
例 3
1 行だけの
面接での聞かれ方
面接では、
- 「X と O の
盤で、 X に 囲まれた O の 領域を 全部 X に 書き換えてください」 - 囲まれた
領域を 1 つずつ 調べる 書き方を 出すと、 「逆から 考えられませんか? 囲まれていない方が 見つけやすくないですか?」と 聞かれます - LeetCode では
盤を その場で 書き換えますが、 ここでは 書き換えた 盤を 返す形に しています
「囲まれている」の
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、
関わる学び
グラフ探索は、
まず 考える手順
段 1
水たまりを 1 つずつた
段 2
裏から"S") を
段 3
印を"O" を"X" に、"S" を"O" に
段 4
たどるのは、
解答
Python
Python
def surround_regions(board):
h = len(board)
w = len(board[0])
g = [list(s) for s in board]
todo = []
for r in range(h):
todo.append((r, 0))
todo.append((r, w - 1))
for c in range(w):
todo.append((0, c))
todo.append((h - 1, c))
# 端から届く水に印 S を付ける
while todo:
r, c = todo.pop()
if not (0 <= r < h and
0 <= c < w):
continue
if g[r][c] != "O":
continue
g[r][c] = "S"
todo += [(r + 1, c),
(r - 1, c),
(r, c + 1),
(r, c - 1)]
return ["".join(row)
.replace("O", "X")
.replace("S", "O")
for row in g]JavaScript
JavaScript
function surroundRegions(
board,
) {
const h = board.length;
const w = board[0].length;
const g = board.map(
(row) => [...row]);
const todo = [];
for (let r = 0; r < h; r++) {
todo.push([r, 0]);
todo.push([r, w - 1]);
}
for (let c = 0; c < w; c++) {
todo.push([0, c]);
todo.push([h - 1, c]);
}
// 端から届く水に印 S を付ける
while (todo.length) {
const [r, c] = todo.pop();
if (r < 0 || r >= h ||
c < 0 || c >= w) {
continue;
}
if (g[r][c] !== "O") {
continue;
}
g[r][c] = "S";
todo.push(
[r + 1, c], [r - 1, c],
[r, c + 1], [r, c - 1]);
}
return g.map((row) => row
.join("")
.replaceAll("O", "X")
.replaceAll("S", "O"));
}Go
Go
import "strings"
func surroundRegions(
board []string,
) []string {
h := len(board)
w := len(board[0])
g := make([][]byte, h)
for r := range board {
g[r] = []byte(board[r])
}
todo := [][2]int{}
for r := 0; r < h; r++ {
todo = append(todo,
[2]int{r, 0},
[2]int{r, w - 1})
}
for c := 0; c < w; c++ {
todo = append(todo,
[2]int{0, c},
[2]int{h - 1, c})
}
// 端から届く水に印 S を付ける
for len(todo) > 0 {
p := todo[len(todo)-1]
todo = todo[:len(todo)-1]
r, c := p[0], p[1]
if r < 0 || r >= h ||
c < 0 || c >= w {
continue
}
if g[r][c] != 'O' {
continue
}
g[r][c] = 'S'
todo = append(todo,
[2]int{r + 1, c},
[2]int{r - 1, c},
[2]int{r, c + 1},
[2]int{r, c - 1})
}
out := []string{}
for _, row := range g {
s := strings.ReplaceAll(
string(row), "O", "X")
out = append(out,
strings.ReplaceAll(
s, "S", "O"))
}
return out
}置き換えは、O を X に、S を O に
計算量
- 時間は O(m × n) です。
m と n は 行と 列の 数です。 印を 付けるのは、 どの マスも 1 回までで、 積まれる 回数も 上下左右からの 4 回までです。 最後の 置き換えも 全部の マスを 1 回ずつ見ます - 使う
記憶は O(m × n) です。 書き換える ための 盤の 写しと、 スタックに 積むマスです
よく ある間違い
- 端の
水を 始めにしない。 囲まれた 水たまりを 内側から 探すと、 端に 届くかを 調べ終えるまで 埋めるか 決められず、 書き方が ややこしくなります - 置き換えの
順を 逆にする。 SをOに戻してから OをXにすると、 残すはずの 水まで 全部土になります - 斜めにもたどる。
斜めで つながる 水は 別の 水たまりです。 斜めに たどると、 囲まれた 水たまりを 端と つながったと 見て 残してしまいます - 1 行や 1 列の
盤で困る。 どの マスも 端に あるので、 全部の 水が 残ります。 4 辺を 始めに する 書き方なら、 そのまま 正しく動きます
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「囲まれた
水を 探す 代わりに、 残る 水を 探します。 盤の 4 辺の マスから 水を たどり、 届いた 水に S の 印を 付けます。 その あと 盤を 1 回見て、 印の 無い O を X に、 S を O に 戻します。 どの マスも 数回しか 見ないので、 時間も 記憶も O(m × n) です。」
書き終えたら、
「例 1 で、
4 辺の 水は 上の 行の 右から 2 つ目だけです。 そこから 下は 土なので、 印は その 1 マスだけです。 まん中の 3 マスの 水には 印が 付かないので 土にし、 印の 1 マスを 水に 戻します。」