問題
始めのbegin、goal、words をbegin からgoal にwords にbegin と goal も
- 着けない
ときは 0を返します。 goalがwordsに無い ときも 0です beginはwordsに入っていなくてもかまいません。 beginとgoalは違う言葉です - 言葉は
どれも 英小文字 (a から z) だけで、 長さは みな 同じ、 1 以上 10 以下です wordsの長さは 1 以上 5000 以下で、 同じ 言葉は 2 回出てきません
入力と出力の例
例 1
cold、cord、card、ward、warm と
入力
begin = "cold"
goal = "warm"
words = ["cord", "card",
"ward", "warm",
"word", "worm"]出力
5例 2
pen から pin へは 1 文字のpin が
入力
begin = "pen"
goal = "pin"
words = ["pan", "pun"]出力
0例 3
lake、like、lime と 3 つでbake や lane へも
入力
begin = "lake"
goal = "lime"
words = ["bake", "lane",
"like", "lime"]出力
3面接での聞かれ方
面接では、
- 「1 文字ずつ
変えて、 ある 言葉から 別の 言葉まで 最短 何手で 行けますか」 - 書き終えた
あとに 「隣の 言葉を 見つける 手間は どれくらいですか?」 「一覧が 5000 語でも 間に 合いますか?」と、 隣を 探す部分の 計算量を 詰めて 聞かれます - 「両端から
同時に 広げると、 どれくらい 速くなりますか?」 「実際の 渡り方も 返せますか?」と、 さらに 広げて 聞かれる こともあります
数えるのが
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは、begin からgoal を
段 2
難しいのは、
段 3
そこで、* にcold は *old、c*ld、co*d、col* の 4 つの
段 4
一度開けた
解答
Python
Python
from collections import deque
def masks(w):
for i in range(len(w)):
a, b = w[:i], w[i + 1:]
yield a + "*" + b
def take(box, w):
out = []
for k in masks(w):
out += box.pop(k, [])
return out
def ladder_length(
begin, goal, words
):
box = {}
for w in words:
for k in masks(w):
if k not in box:
box[k] = []
box[k].append(w)
dist = {begin: 1}
queue = deque([begin])
while queue:
w = queue.popleft()
if w == goal:
return dist[w]
d = dist[w] + 1
for v in take(box, w):
if v in dist:
continue
dist[v] = d
queue.append(v)
return 0JavaScript
JavaScript
function masks(w) {
const out = [];
const n = w.length;
for (let i = 0; i < n; i++) {
const a = w.slice(0, i);
const b = w.slice(i + 1);
out.push(a + "*" + b);
}
return out;
}
function take(box, w) {
const out = [];
for (const k of masks(w)) {
const g = box.get(k) || [];
box.delete(k);
out.push(...g);
}
return out;
}
function ladderLength(
begin, goal, words
) {
const box = new Map();
for (const w of words) {
for (const k of masks(w)) {
if (!box.has(k)) {
box.set(k, []);
}
box.get(k).push(w);
}
}
const dist = new Map();
dist.set(begin, 1);
const queue = [begin];
let head = 0;
while (head < queue.length) {
const w = queue[head];
head++;
if (w === goal) {
return dist.get(w);
}
const d = dist.get(w) + 1;
const next = take(box, w);
for (const v of next) {
if (dist.has(v)) continue;
dist.set(v, d);
queue.push(v);
}
}
return 0;
}Go
Go
func masks(w string) []string {
out := []string{}
for i := range w {
k := w[:i] + "*" + w[i+1:]
out = append(out, k)
}
return out
}
func take(
box map[string][]string,
w string,
) []string {
out := []string{}
for _, k := range masks(w) {
out = append(out, box[k]...)
delete(box, k)
}
return out
}
func ladderLength(
begin, goal string,
words []string,
) int {
box := map[string][]string{}
for _, w := range words {
for _, k := range masks(w) {
box[k] = append(box[k], w)
}
}
dist := map[string]int{}
dist[begin] = 1
queue := []string{begin}
head := 0
for head < len(queue) {
w := queue[head]
head++
if w == goal {
return dist[w]
}
d := dist[w] + 1
next := take(box, w)
for _, v := range next {
if _, ok := dist[v]; ok {
continue
}
dist[v] = d
queue = append(queue, v)
}
}
return 0
}take は、goal が0 でbegin が
計算量
- 時間は O(n × L²) です。
箱に 分ける 時、 n 個の 言葉それぞれに L 個の 伏せ形を 作り、 1 つ作るのに 長さ L の 手間が かかります。 幅優先でも、 どの 言葉も 1 回だけ 取り出され、 そのたびに 伏せ形を L 個作ります。 箱は 1 回開けたら 捨てるので、 箱の 中を 見る 手間は 全体で O(n × L) に 収まります - 使う
記憶は O(n × L²) です。 箱の 鍵が 全部で n × L 個まであり、 どれも 長さ L の 文字列です - 段 2 の
全部比べる 書き方は O(n² × L) です。 L は 10 以下と 小さく、 n は 5000 まであるので、 n を 2 乗しない 形に したことが 効いています
よく ある間違い
- 取り出す時に
印を付ける。 同じ 言葉が、 いく つもの 言葉から 何度も 待ち行列に 入ります。 答えは 合っても、 手間が ふくらみます。 入れた 時に 手数を 辞書へ 書き、 それを 印にします - 開けた
箱を 残しておく。 箱を 捨てないと、 同じ 箱が、 その箱の 言葉の 数だけ 何度も 開けられます。 たとえば *atの箱に 26 語あると、 その 26 語を 取り出すたびに 26 語を 見直し、 箱の 大きさの 2 乗の 手間になります goalが一覧に 無い 場合を 特別扱いして 書き間違える。 着けたか どうかを、 goalとの 1 文字違いで判定すると、 一覧に 無い goalへ渡ってしまいます。 例 2 のように、 渡る 先は 必ず 一覧の中です - 再帰の
深さ優先で 最短を探す。 深さ優先で 最初に 着いた 道は 最短とは 限らず、 全部の 道を 試すと 時間が 足りません。 言葉が 5000 個並ぶと、 Python では 再帰の 上限にも かかります
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「言葉を
点、 1 文字違いを 線と 見ると、 どの 線も 1 手なので 幅優先で 最短が 求まります。 隣を 一覧と 全部比べると O(n² × L) なので、 1 文字を 伏せた形で 言葉を 箱に 分け、 伏せ形を L 個作って 箱を 開けるだけで 隣を 集めます。 開けた箱は 捨てるので、 全体で O(n × L²) です。」
書き終えたら、
「例 3 で
たどります。 lakeの伏せ形の 箱を 開けると、 *akeからbake、la*eからlane、l*keからlikeが出て、 どれも 手数 2 で 入ります。 bakeとlaneからは新しい 言葉が 出ません。 likeのli*eの箱から limeが手数 3 で 入り、 取り出した 時に goalなので 3 を返します。」
