diff --git a/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/README.md b/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/README.md new file mode 100644 index 00000000..a49ed49c --- /dev/null +++ b/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/README.md @@ -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 要素) + +--- diff --git a/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/RegularExpressionMatching.py b/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/RegularExpressionMatching.py new file mode 100644 index 00000000..70aec0c6 --- /dev/null +++ b/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/RegularExpressionMatching.py @@ -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`)版や、図解を含めたステップバイステップ解析も可能です。 diff --git a/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/RegularExpressionMatching.ts b/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/RegularExpressionMatching.ts new file mode 100644 index 00000000..ad02b29d --- /dev/null +++ b/Algorithm/DynamicProgramming/leetcode/10. Regular Expression Matching/RegularExpressionMatching.ts @@ -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 +// ``` \ No newline at end of file diff --git a/Algorithm/Meet-in-the-middle/atcoder/B14/B14.go b/Algorithm/Meet-in-the-middle/atcoder/B14/B14.go new file mode 100644 index 00000000..b6caca53 --- /dev/null +++ b/Algorithm/Meet-in-the-middle/atcoder/B14/B14.go @@ -0,0 +1,123 @@ +// 以下に、**Go (go 1.20.6)** による解答を提示します。 +// 制約に対応するために、**半分全列挙(Meet-in-the-middle)** を用いた効率的な実装です。 +// 型を明示し、処理時間・メモリ使用量を考慮しています。 + +// ## ✅ 解法概要 + +// * 与えられた整数列を **前半・後半に分割** +// * 各部分の **部分集合和を全列挙**(最大 2^15 = 32,768 通り) +// * 一方の部分和リストに対して **二分探索**を使用し、合計が `K` になる組を探索 + +// ## ✅ Go実装(Go 1.20.6) + +package main + +import ( + "bufio" + "fmt" + "os" + "sort" + "strconv" + "strings" +) + +// getSubsetSums returns all possible subset sums of arr. +func getSubsetSums(arr []int) []int { + n := len(arr) + res := make([]int, 0, 1<> 1; + if (arr[mid] === target) return true; + if (arr[mid] < target) left = mid + 1; + else right = mid - 1; + } + return false; +} + +function main() { + const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split(/\s+/).map(Number); + const N = input[0]; + const K = input[1]; + const A = input.slice(2); + + const mid = Math.floor(N / 2); + const left = A.slice(0, mid); + const right = A.slice(mid); + + const leftSums = getSubsetSums(left); + const rightSums = getSubsetSums(right); + rightSums.sort((a, b) => a - b); + + for (const x of leftSums) { + const remain = K - x; + if (binarySearch(rightSums, remain)) { + console.log("Yes"); + return; + } + } + + console.log("No"); +} + +main(); + +// ### ✅ 実行例 + +// 入力: + +// ``` +// 6 30 +// 5 1 18 7 2 9 +// ``` + +// 出力: + +// ``` +// Yes +// ``` + +// --- + +// ### ✅ 時間・空間計算量 + +// * 時間: `O(2^(N/2) * log(2^(N/2)))` ≒ `2^15 * 15` → 高速(制限内) +// * 空間: `O(2^(N/2))` → 最大 32,768 個(メモリ制限内) + diff --git a/Algorithm/Meet-in-the-middle/atcoder/B14/B14.php b/Algorithm/Meet-in-the-middle/atcoder/B14/B14.php new file mode 100644 index 00000000..25dd8ca5 --- /dev/null +++ b/Algorithm/Meet-in-the-middle/atcoder/B14/B14.php @@ -0,0 +1,110 @@ +