動的計画法で ドットと アスタリスクの 入った 型が 文字列全体に 合うか調べる
「どの
海外で定番の問題リスト 4 本には入っていません
問題
文字列 s とp をp が s の
- 英小文字は、
同じ 文字 1 つに合います .(ドット) は、どの 英小文字 1 つにも合います *(アスタリスク) は、すぐ前の 1 文字 (英小文字か .) を 0 回以上くり返します。a*は空、 a、 aa、 aaa、 … の どれにも合います - 合うのは
sの全体だけです。 sの一部だけに 合っても falseです sの長さは 0 以上 20 以下で 英小文字だけ、 pの長さは 1 以上 20 以下で 英小文字と .と*だけです*はpの先頭には 来ず、 *が 2 つ続くこともありません
入力と出力の例
例 1
b が b に、a* が aaa に、
例 2
a* は a を
例 3
z* も y* も 0 回くり返す (空) と
面接での聞かれ方
面接では、
- 「
.と*だけの簡単な 正規表現が、 文字列全体に 合うかを 判定してください」 - 最初に
「 *は直前の 1 文字だけに かかりますか?」 「一部が 合えば よいですか、 全体ですか?」と 確かめられるかを 見られます - 書き終えると
「 .*はどう 動きますか?」 「空の 文字列と a*b*は?」と端の 場合を 聞かれます
* が
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「動的計画法 (DP)」に、
関わる学び
動的計画法は、
まず 考える手順
段 1
* のa*a*a*… のような
段 2
s のp のok[i][j] とok[0][0] (どちらもp の j 文字目が. なら、s の i 文字目と合い、ok[i-1][j-1] が
段 3
p の j 文字目が * なら、ok[i][j-2] です。s の i 文字目がok[i-1][j] (i 文字目を
段 4
i が 0 (空の s) のa*b* のように* のok[0][j-2] をok[len(s)][len(p)] です。
解答
Python
Python
def fits(ch, pc):
if ch is None:
return False
return pc in (ch, ".")
def step(prev, ch, p):
row = [prev is None]
for j, pc in enumerate(p):
if pc == "*":
v = row[j - 1]
before = p[j - 1]
if fits(ch, before):
v |= prev[j + 1]
else:
v = fits(ch, pc)
v = v and prev[j]
row.append(v)
return row
def pattern_match(s, p):
row = step(None, None, p)
for ch in s:
row = step(row, ch, p)
return row[-1]JavaScript
JavaScript
function fits(ch, pc) {
if (ch === null) {
return false;
}
if (pc === ".") {
return true;
}
return pc === ch;
}
function step(prev, ch, p) {
const row = [prev === null];
[...p].forEach((pc, j) => {
let v;
if (pc === "*") {
v = row[j - 1];
if (fits(ch, p[j - 1])) {
v = v || prev[j + 1];
}
} else {
v = fits(ch, pc);
v = v && prev[j];
}
row.push(v);
});
return row;
}
function patternMatch(s, p) {
let row = step(null, null, p);
for (const ch of s) {
row = step(row, ch, p);
}
return row[row.length - 1];
}Go
Go
func fits(
has bool, ch, pc byte,
) bool {
if !has {
return false
}
return pc == ch || pc == '.'
}
func step(
prev []bool,
ch byte, p string,
) []bool {
has := prev != nil
row := []bool{!has}
for j := 0; j < len(p); j++ {
pc := p[j]
v := false
if pc == '*' {
v = row[j-1]
if fits(has, ch, p[j-1]) {
v = v || prev[j+1]
}
} else {
v = fits(has, ch, pc) &&
prev[j]
}
row = append(row, v)
}
return row
}
func patternMatch(
s, p string,
) bool {
row := step(nil, 0, p)
for i := 0; i < len(s); i++ {
row = step(row, s[i], p)
}
return row[len(p)]
}表は 1 step は 1 つ前のprev (段 2 の ok[i-1]) と、s の i 文字目 ch から、row (ok[i]) をs がNone、null、nil) でfits が
p の j 番 (0 から* のrow[j - 1] をch がprev[j + 1] を* はj - 1 は 0 以上です。ch にprev[j] が
計算量
- 時間は O(m × n) です (m は
s、n は pの長さ)。 表の 欄を 1 つずつ、 決まった 数の 比べで埋めます - 使う
記憶は O(n) です。 生きているのは 1 つ前の 行と 今の 行の 2 本だけです。 表を 全部持つ 書き方は O(m × n) です - 段 1 の
全部試す再帰は、 sが a ばかりでpがa*a*a*…bのような形で 指数的に 遅くなります
よく ある間違い
*を「任意の文字列」と読む。 ファイル名の *とは違い、 ここの *は直前の 1 文字のくり返しです。 例 2 で trueを返したら、 この 読み違いです - 0 回の
場合を忘れる。 *の欄で ok[i-1][j]だけを見ると、 例 3 の 空の 文字列や s = "c"、p = "a*c"に合わなくなります。 組ごと 消す ok[i][j-2]も見ます - 空の
sの行を飛ばす。 i を 1 から 始めると ok[0][j]が全部偽のままに なり、 a*で始まる 型が 合わなくなります - 一部が
合えば 真にする。 合うのは 全体だけです。 s = "abc"、p = "ab"はfalseです
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「
sの前から i 文字と pの前から j 文字が 合うかを 表にします。 ふつうの 文字か .なら、1 文字合えば 左上を 写します。 *なら直前の 文字と 組で 読み、 組を 0 回使う 2 つ左の 欄か、 sの文字が 組に 合う ときに 1 つ上の 欄の どちらかが 真なら真です。 空どうしを 真として 始め、 時間は O(m × n) です。」
書き終えたら、
「例 1 の
baaadとba*dでたどります。 b どうしで 真、 次の aは 1 つ目の a に合い、 *の欄は 0 回なら b まで、 1 回以上 なら 上の 行の *の欄を 写すので、 a が いくつ続いても 真のままです。 最後の d が d に 合い、 右下が 真に なります。」