問題
m 行 n 列のheights をheights[r][c] は、
ある
- マスは
[行, 列]の 2 つの数で 表し、 行の 小さい 順、 同じ 行は 列の 小さい 順に 並べて返します - m と n は 1 以上 200 以下、
高さは どれも 0 以上 100000 以下の整数です
入力と出力の例
例 1
左上の 1 と 2 は、
入力
heights = [[1, 2, 3],
[8, 9, 4],
[7, 6, 5]]出力
[[0, 2], [1, 0], [1, 1],
[1, 2], [2, 0], [2, 1],
[2, 2]]例 2
まん中の 1 は
入力
heights = [[3, 3, 3],
[3, 1, 3],
[3, 3, 3]]出力
[[0, 0], [0, 1], [0, 2],
[1, 0], [1, 2],
[2, 0], [2, 1], [2, 2]]例 3
マスが 1 つだけの
入力
heights = [[5]]出力
[[0, 0]]面接での聞かれ方
面接では、
- 「高さの
地図で、 2 つの 海の 両方 へ水が 流れ出せる 場所を 全部返してください」 - 書き終えた
あとに 「マスごとに 水を 流してみる 書き方と 比べて、 どれだけ 速いですか?」 「なぜ海の 側から 逆に たどるのですか?」と 続けて 聞かれます - 「同じ
高さの マスへは 流れますか?」と、 条件の 端を 確かめられる こともあります
同じ
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは
段 2
向きを
段 3
東の
段 4
角の
解答
Python
Python
from collections import deque
STEPS = [
(1, 0), (-1, 0),
(0, 1), (0, -1),
]
def reach(h, starts):
m, n = len(h), len(h[0])
seen = set(starts)
queue = deque(starts)
while queue:
r, c = queue.popleft()
here = h[r][c]
for dr, dc in STEPS:
a = r + dr
b = c + dc
if not 0 <= a < m:
continue
if not 0 <= b < n:
continue
if (a, b) in seen:
continue
if h[a][b] < here:
continue
seen.add((a, b))
queue.append((a, b))
return seen
def both_seas(heights):
m = len(heights)
n = len(heights[0])
west = []
east = []
for r in range(m):
west.append((r, 0))
east.append((r, n - 1))
for c in range(n):
west.append((0, c))
east.append((m - 1, c))
a = reach(heights, west)
b = reach(heights, east)
both = a & b
out = []
for r in range(m):
for c in range(n):
if (r, c) in both:
out += [[r, c]]
return outJavaScript
JavaScript
const STEPS = [
[1, 0], [-1, 0],
[0, 1], [0, -1],
];
function reach(h, starts) {
const n = h[0].length;
const seen = new Set();
for (const [r, c] of starts) {
seen.add(r * n + c);
}
const queue = [...starts];
let head = 0;
while (head < queue.length) {
const [r, c] = queue[head];
head++;
const here = h[r][c];
for (const s of STEPS) {
const a = r + s[0];
const b = c + s[1];
const v = h[a]?.[b] ?? -1;
if (v < here) continue;
const k = a * n + b;
if (seen.has(k)) continue;
seen.add(k);
queue.push([a, b]);
}
}
return seen;
}
function bothSeas(heights) {
const h = heights;
const m = h.length;
const n = h[0].length;
const west = [];
const east = [];
for (let r = 0; r < m; r++) {
west.push([r, 0]);
east.push([r, n - 1]);
}
for (let c = 0; c < n; c++) {
west.push([0, c]);
east.push([m - 1, c]);
}
const a = reach(h, west);
const b = reach(h, east);
const out = [];
h.forEach((row, r) => {
row.forEach((_, c) => {
const k = r * n + c;
if (!a.has(k)) return;
if (!b.has(k)) return;
out.push([r, c]);
});
});
return out;
}Go
Go
type cell = [2]int
var dirs = [4]cell{
{1, 0}, {-1, 0},
{0, 1}, {0, -1},
}
func reach(
h [][]int, starts []cell,
) [][]bool {
m, n := len(h), len(h[0])
seen := make([][]bool, m)
for r := range seen {
seen[r] = make([]bool, n)
}
for _, p := range starts {
seen[p[0]][p[1]] = true
}
queue := starts
head := 0
for head < len(queue) {
p := queue[head]
head++
r, c := p[0], p[1]
here := h[r][c]
for _, d := range dirs {
a, b := r+d[0], c+d[1]
if a < 0 || a >= m {
continue
}
if b < 0 || b >= n {
continue
}
if seen[a][b] {
continue
}
if h[a][b] < here {
continue
}
seen[a][b] = true
nx := cell{a, b}
queue = append(queue, nx)
}
}
return seen
}
func bothSeas(
heights [][]int,
) [][]int {
m := len(heights)
n := len(heights[0])
west := []cell{}
east := []cell{}
for r := 0; r < m; r++ {
w := cell{r, 0}
e := cell{r, n - 1}
west = append(west, w)
east = append(east, e)
}
for c := 0; c < n; c++ {
w := cell{0, c}
e := cell{m - 1, c}
west = append(west, w)
east = append(east, e)
}
a := reach(heights, west)
b := reach(heights, east)
out := [][]int{}
for r := 0; r < m; r++ {
for c := 0; c < n; c++ {
if a[r][c] && b[r][c] {
p := []int{r, c}
out = append(out, p)
}
}
}
return out
}JavaScript の h[a]?.[b] ?? -1 は、-1 に-1 はforEach のreturn は、
計算量
- 時間は O(m × n) です。
幅優先を 2 回しますが、 どちらでも、 どの マスも 待ち行列に 1 回だけ 入り、 出る たびに 4 方向の 隣を 見るだけです。 最後に 地図を 1 回なめて 答えを集めます - 使う
記憶は O(m × n) です。 海ごとの 「届いた」印と、 待ち行列です - 段 1 の
マスごとに 流す 書き方は O((m × n)²) です。 海が 2 つしかない ことを 使い、 出発点の 側を 入れ替えた ことで、 ここまで 減りました
よく ある間違い
- 登る
向きの 比べ方を 逆にする。 海から 逆に たどる 時は、 隣が 「同じか高い」なら 進みます。 流す 向きのまま 「低い」なら 進むと 書くと、 まったく 違う マスを集めます - 同じ
高さを 止めてしまう。 「高い」だけで 進むと、 例 2 のように 同じ 高さが 続く 所で 止まり、 届くはずの マスを 落とします。 比べるのは 「より 低ければ 止める」です - 角を
片方の 海に しか 入れない。 右上の 角を、 上の 端として 西の 海に だけ 入れ、 右の 端としての 東の 海を 忘れると、 1 行や 1 列の 地図で 答えが 欠けます。 行ごとと 列ごとに 両方の 端を 入れれば、 角は 自然に 両方 へ入ります - 集めた
場所を、 集めた 順のまま返す。 幅優先で 届いた 順は、 決まりの 順とは 違います。 最後に 地図を 行と 列の 順に なめて並べます
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「マスごとに
水を 流すと、 マスの 数の 2 乗かかります。 そこで 向きを 逆に して、 それぞれの 海に 面した マスから、 同じか 高い 隣へ 登る 幅優先を します。 届いた マスは、 その 海へ 流れ込める マスです。 2 回の 幅優先で 両方に 届いた マスを、 行と 列の 順に 集めます。 時間も 記憶も O(m × n) です。」
書き終えたら、
「例 1 の
東の 海で たどります。 下の 端の 7、 6、 5 と、 右の 端の 3、 4 から 始めます。 4 からは 隣の 9 へ 登れ、 7 からは 上の 8 へ 登れます。 3 の 左の 2 と、 8 の 上の 1 は 低いので 登れません。 西の 海からは 全部の マスに 届くので、 答えは 左上の 2 マスを 除いた 7 マスです。」
