Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
**JavaScript + DP解法** を用いて処理し、**各処理を図を使って具体的に説明**します。

---

## 🎯 問題概要(再掲)

* `s`:マッチさせたい文字列(例: `"aa"`)
* `p`:正規表現パターン(例: `"a*"`)

**正規表現ルール**:

| 記号 | 意味 |
| --- | ----------------- |
| `.` | 任意の1文字にマッチ |
| `*` | 直前の文字の 0 回以上の繰り返し |

---

## 🧠 解法方針:動的計画法(DP)

### 🧩 状態定義

* `dp[i][j] = true`
⇔ `s[0..i-1]`(長さ i の prefix)と `p[0..j-1]`(長さ j の prefix)がマッチする

---

## 🧮 例:`s = "aa"`、`p = "a*"`

### 初期状態

```
s = " a a"
↑ ↑
i= 1 2

p = " a *"
↑ ↑
j= 1 2
```

### DP テーブルの初期化

| | "" | a | \* |
| -- | -- | - | -- |
| "" | T | F | T |
| a | F | T | T |
| a | F | F | T |

---

### 📊 ステップ 1:`dp[0][0] = true`

空文字列同士は常にマッチ。

```
dp[0][0] = true
```

---

### 📊 ステップ 2:パターンに `*` がある場合の初期化

* `dp[0][2] = dp[0][0] = true`(`a*` が空文字列にマッチ)

```
Pattern: "a*"
→ '*' は前の 'a' を0回とみなせるのでOK
```

---

### 📊 ステップ 3:DPテーブルの構築(再帰的に状態更新)

```js
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
...
}
}
```

### 更新規則(図示)

#### 🔹 case1: 文字一致 or `.`(1文字マッチ)

```
if (p[j-1] === s[i-1] || p[j-1] === '.')
→ dp[i][j] = dp[i-1][j-1]
```

```
s: ... a
p: ... a
↑ ↑
i j
```

---

#### 🔹 case2: `*` の場合(0回 or 1回以上)

```
if (p[j-1] === '*') {
dp[i][j] = dp[i][j-2] // * を0回使用
if (p[j-2] === s[i-1] || p[j-2] === '.')
dp[i][j] |= dp[i-1][j] // * を1回以上使用
}
```

```
Pattern: a*
↑↑
j-2 j-1

s: a a
i-1
```

---

## ✅ 最終的なDPテーブル(例:s="aa", p="a\*")

| | "" | a | \* |
| -- | -- | - | -- |
| "" | ✅ | ❌ | ✅ |
| a | ❌ | ✅ | ✅ |
| a | ❌ | ❌ | ✅ |

→ `dp[2][2] = true`(完全一致)

---

## 🖼️ 図による処理まとめ

```
s = "aa"
p = "a*"

1. dp[0][0] = true // 空文字列同士マッチ

2. dp[0][2] = true // a* → *は0回で空文字にマッチ

3. dp[1][1] = true // s[0] = a, p[0] = a

4. dp[1][2] = true // '*' で1回以上の a

5. dp[2][2] = true // '*' でさらに1回
```

---

## 📌 結果

```js
console.log(dp[s.length][p.length]); // true
```

---

## 🔁 応用例

### s = "ab", p = ".\*"

* `.` → 任意の文字
* `*` → 何回でも繰り返し → すべての文字列にマッチ

```
dp[2][2] = true
```

---

## 🧠 メモリ&計算量

* 時間:O(m × n)
* 空間:O(m × n)

* 制約が 20 以下なので問題なし(最大 400 要素)

---
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 以下は、LeetCodeの「正規表現マッチング」問題に対する **Python(CPython 3.11.4対応)** の解答です。
# **型アノテーション付き**で記述し、**計算量**と**メモリ使用**にも配慮しています。

# ## ✅ 問題概要(再掲)

# * `.` は任意の1文字
# * `*` は直前の文字の 0 回以上の繰り返し
# * `s` 全体と `p` 全体がマッチするかを判定

# ---

# ## 🧠 解法方針:動的計画法(DP)

# * 状態:`dp[i][j]` ⇒ `s[:i]` と `p[:j]` がマッチするか
# * 遷移:

# * `p[j - 1] == '*'`:

# * `dp[i][j] = dp[i][j - 2] or (dp[i - 1][j] if p[j - 2] matches s[i - 1])`
# * それ以外:

# * `dp[i][j] = dp[i - 1][j - 1]` if `p[j - 1] == s[i - 1]` or `p[j - 1] == '.'`

# ## 🐍 Python実装(型付き)

from typing import List

class Solution:
def isMatch(self, s: str, p: str) -> bool:
m: int = len(s)
n: int = len(p)

# dp[i][j] := s[:i] と p[:j] がマッチするか
dp: List[List[bool]] = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True # 空文字同士はマッチ

# パターン初期化(空文字にマッチする * の処理)
for j in range(2, n + 1):
if p[j - 1] == '*':
dp[0][j] = dp[0][j - 2]

# DP更新
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j - 1] == '.' or p[j - 1] == s[i - 1]:
dp[i][j] = dp[i - 1][j - 1]
elif p[j - 1] == '*':
dp[i][j] = dp[i][j - 2] # * を 0 回使う
if p[j - 2] == '.' or p[j - 2] == s[i - 1]:
dp[i][j] |= dp[i - 1][j] # * を 1 回以上使う

return dp[m][n]

# ## 🔎 使用例

# ```python
# if __name__ == "__main__":
# sol = Solution()
# print(sol.isMatch("aa", "a")) # False
# print(sol.isMatch("aa", "a*")) # True
# print(sol.isMatch("ab", ".*")) # True
# ```

# ---

# ## ⏱ 計算量・メモリ消費

# | 項目 | 値 |
# | --------- | ----------------------- |
# | 時間計算量 | O(m × n) |
# | 空間計算量 | O(m × n) |
# | 入力サイズ最大 | 20文字(s, p) |
# | 最大DP表サイズ | 21 × 21 = 441セル |
# | メモリ消費(概算) | 約 441 × 1バイト ≈ **500B** |

# ## ✅ 補足:なぜDPか?

# * 再帰+メモ化でも解けるが、LeetCodeではTLEになりやすい。
# * 全探索で部分一致を記録できるDPが最も安定した手法。

# ご希望があれば、再帰+メモ化(`lru_cache`)版や、図解を含めたステップバイステップ解析も可能です。
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 以下は、LeetCodeの正規表現マッチング問題に対する **JavaScript(Node.js)での解答例** です。`fs` モジュールを用いて標準入力から `s` と `p` を受け取り、動的計画法(DP)で `.` や `*` を考慮した完全一致の判定を行います。

// ---

// ### ✅ 解法方針(動的計画法・2次元DP)

// * `dp[i][j]`:`s[0..i-1]` と `p[0..j-1]` がマッチするかを表す。
// * 状態遷移:

// * `p[j - 1] === '*'` の場合:

// * 無視:`dp[i][j] = dp[i][j - 2]`
// * 一致していて繰り返し使用:`dp[i][j] |= dp[i - 1][j]`
// * `p[j - 1] === s[i - 1] || p[j - 1] === '.'` の場合:

// * `dp[i][j] = dp[i - 1][j - 1]`

// ---

// ### 📦 JavaScript(Node.js)実装(`fs` 使用)

// ```javascript
const fs = require('fs');

// 入力読み込み
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
const s = input[0]; // 入力文字列 s
const p = input[1]; // パターン文字列 p

function isMatch(s, p) {
const m = s.length;
const n = p.length;

// 初期化:false埋め、dp[0][0] = true(空文字同士は一致)
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(false));
dp[0][0] = true;

// 空文字列 s に対するパターン p の初期化(例: a*, a*b*, a*b*c*)
for (let j = 2; j <= n; j++) {
if (p[j - 1] === '*') {
dp[0][j] = dp[0][j - 2];
}
}

// DPテーブル更新
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (p[j - 1] === '.' || p[j - 1] === s[i - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else if (p[j - 1] === '*') {
dp[i][j] = dp[i][j - 2]; // * を0回使用
if (p[j - 2] === '.' || p[j - 2] === s[i - 1]) {
dp[i][j] = dp[i][j] || dp[i - 1][j]; // * を1回以上使用
}
}
}
}

return dp[m][n];
}

// 出力
console.log(isMatch(s, p));

// ### 🧪 入力方法(例)

// 以下のような入力ファイルまたは標準入力で動作します:

// ```
// aa
// a*
// ```

// 出力:

// ```
// true
// ```

// ---

// ### ⏱ 性能(最悪計算量)

// * 時間計算量:O(m × n)
// * 空間計算量:O(m × n)
// * 制約が小さい(s, p ≤ 20)ので高速かつメモリ効率良好です。

// ---

// 必要であれば、標準入力ファイルを指定してテスト可能です。Node.js環境で実行する場合:

// ```bash
// echo -e "ab\n.*" | node solution.js
// ```
Loading