問題
言葉をWordBook を
addWord(w)は、言葉 wをしまいます。 何も 返しません search(w)は、しまった 言葉の うち wに当てはまる 物が 1 つでも あれば 真、 無ければ 偽を 返します。 wの中の .は、どの 1 文字にも 当たる 伏せ字です。 長さは 同じでなければ 当てはまりません
メソッドのadd_word と search、AddWord と Search です。newWordBook() で
- しまう
言葉は 英小文字 (a から z) だけ、 探す 言葉は 英小文字と .だけです - 長さは
どれも 1 以上 25 以下です - 探す
言葉に 入る .は、1 つの 言葉に つき 2 つまでです - 操作は
全部で 10000 回以下です
例は、ops と、args で"WordBook" はaddWord は null です。
入力と出力の例
例 1
"r.pe" は "rope" にも "ripe" にも"rop" は
入力
ops = ["WordBook", "addWord",
"addWord", "search",
"search", "search"]
args = [[], ["rope"],
["ripe"], ["r.pe"],
["rop"], ["..pe"]]出力
[null, null, null, true,
false, true]例 2
伏せ字は 1 文字ちょうどに
入力
ops = ["WordBook", "addWord",
"addWord", "search",
"search", "search"]
args = [[], ["go"], ["gold"],
["g.."], ["g..d"], ["."]]出力
[null, null, null, false,
true, false]例 3
何もしまっていないうちは、
入力
ops = ["WordBook", "search",
"addWord", "search",
"search"]
args = [[], ["."], ["a"],
["."], ["a"]]出力
[null, false, null, true,
true]面接での聞かれ方
面接では、
- 「辞書に
言葉を 足す操作と、 .を伏せ字に して探す操作を 作ってください」 - 書き終えた
あとに 「伏せ字が 多いと、 どこまで 遅くなりますか?」 「言葉を 全部並べて 比べる 書き方と、 どちらが 速いですか?」と、 計算量の 見積もりを 聞かれます - 「
*(何文字でも当たる 伏せ字) も 入れるとしたら、 どう 変わりますか?」と、 形を 広げて 聞かれる こともあります
伏せ字が
アルゴリズムのチートシートの「ハッシュマップ」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは
段 2
伏せ字が
段 3
分かれた
段 4
最後の
解答
Python
Python
class BookNode:
def __init__(self):
self.kids = {}
self.end = False
def step(nodes, ch):
nxt = []
for node in nodes:
kids = node.kids
if ch == ".":
nxt += kids.values()
elif ch in kids:
nxt.append(kids[ch])
return nxt
class WordBook:
def __init__(self):
self.root = BookNode()
def add_word(self, w):
node = self.root
for ch in w:
kids = node.kids
nx = kids.get(ch)
if nx is None:
nx = BookNode()
kids[ch] = nx
node = nx
node.end = True
def search(self, w):
cur = [self.root]
for ch in w:
cur = step(cur, ch)
if not cur:
return False
for node in cur:
if node.end:
return True
return FalseJavaScript
JavaScript
class BookNode {
constructor() {
this.kids = new Map();
this.end = false;
}
}
function step(nodes, ch) {
const nx = [];
for (const node of nodes) {
const kids = node.kids;
if (ch === ".") {
nx.push(...kids.values());
} else if (kids.has(ch)) {
nx.push(kids.get(ch));
}
}
return nx;
}
class WordBook {
constructor() {
this.root = new BookNode();
}
addWord(w) {
let node = this.root;
for (const ch of w) {
const kids = node.kids;
let next = kids.get(ch);
if (next === undefined) {
next = new BookNode();
kids.set(ch, next);
}
node = next;
}
node.end = true;
}
search(w) {
let cur = [this.root];
for (const ch of w) {
cur = step(cur, ch);
if (cur.length === 0) {
return false;
}
}
for (const node of cur) {
if (node.end) return true;
}
return false;
}
}Go
Go
type bookNode struct {
kids [26]*bookNode
end bool
}
type WordBook struct {
root *bookNode
}
func newWordBook() *WordBook {
return &WordBook{&bookNode{}}
}
func (b *WordBook) AddWord(
w string,
) {
node := b.root
for _, ch := range w {
i := ch - 'a'
if node.kids[i] == nil {
node.kids[i] = &bookNode{}
}
node = node.kids[i]
}
node.end = true
}
func step(
nodes []*bookNode, ch rune,
) []*bookNode {
nx := []*bookNode{}
for _, node := range nodes {
if ch != '.' {
kid := node.kids[ch-'a']
if kid != nil {
nx = append(nx, kid)
}
continue
}
kids := node.kids
for _, kid := range kids {
if kid != nil {
nx = append(nx, kid)
}
}
}
return nx
}
func (b *WordBook) Search(
w string,
) bool {
cur := []*bookNode{b.root}
for _, ch := range w {
cur = step(cur, ch)
if len(cur) == 0 {
return false
}
}
for _, node := range cur {
if node.end {
return true
}
}
return false
}step は、
計算量
addWordは、言葉の 長さを L として 時間 O(L) です searchは、伏せ字の 無い 文字では 集まりの 大きさが 増えず、 伏せ字 1 つで 多くても 26 倍に なります。 伏せ字が 2 つまでなので、 集まりは 676 個までで、 時間は O(676 × L)、 つまり O(L) です。 しまった 言葉の 数 n には 関係しません - 使う
記憶は、 木が しまった 文字数の 合計 S に 比例して O(S)、 searchの集まりが 多くても 676 個です - 段 1 の
全部比べる 書き方は、 1 回の searchに O(n × L) かかります。言葉が 10000 個に 近いと、 この 差が 効いてきます
よく ある間違い
- 最後に
印を 見ずに 真を返す。 集まりが 空でない ことだけで 真に すると、 "gold"しか無いのに "g.."が真に なります。 たどり 切った 節に、 言葉の 終わりの 印が あるかを見ます - 伏せ字の
数の 上限を 見落として 計算量を言う。 伏せ字が 何個でも よいなら、 集まりは 26 の 何乗にも 増えます。 2 つまでという 条件が あるから O(L) と 言える、と 根拠まで 添えて話します - 再帰で
書いて、 文字列を 切り出しながら渡す。 残りの 文字列を 毎回切り出して 渡すと、 1 段ごとに 写しを 作り、 長さの 2 乗の 手間が かかります。 位置の 番号を 渡すか、 この 解答のように 集まりを 進める 形にします
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「言葉を
全部比べると、 1 回の 検索で O(n × L) かかります。 トライ木にしまえば、 決まった 文字は 子を 1 つたどるだけで、 伏せ字の ところだけ 全部の 子へ 分かれます。 いま当てはまる節の 集まりを 1 文字ず つ進め、 最後に 終わりの 印を 見ます。 伏せ字は 2 つまでなので、 集まりは 676 個を 超えず、 検索は O(L) です。」
書き終えたら、
「例 1 の
"r.pe"でたどります。 最初は 根だけで、 rで r の節に 進みます。 .では r の子の o と i の 2 つへ 分かれ、 pでそれぞれの p へ、 eでそれぞれの e へ 進みます。 どちらの e にも 終わりの 印が あるので 真です。 "rop"は p まで進めますが、 p に 印が 無いので 偽です。」
