diff --git a/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.go b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.go new file mode 100644 index 00000000..6e75dfbe --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.go @@ -0,0 +1,129 @@ +// 以下は Go 1.20.6 を用いた解法です。 +// **制約(N ≤ 100,000)に対応するため、計算量 O(N log N)** のアルゴリズム(2次元LIS)を使用しています。 +// ソートと二分探索を使った効率的な処理で、型を明示し、関数分離も行っています。 + +// --- + +// ## ✅ Go 実装(型明示・関数分離) + +// ```go +package main + +import ( + "bufio" + "fmt" + "os" + "sort" + "strconv" + "strings" +) + +// Box は各箱の構造体。縦と横を持つ。 +type Box struct { + X int // 縦の長さ + Y int // 横の長さ +} + +// maxNestingDepth は最大で何重に箱をネストできるかを求める。 +// @param boxes []Box - 各箱の情報 +// @return int - 最大ネスト数 +func maxNestingDepth(boxes []Box) int { + // X昇順、Xが同じならY降順でソート + sort.Slice(boxes, func(i, j int) bool { + if boxes[i].X == boxes[j].X { + return boxes[i].Y > boxes[j].Y + } + return boxes[i].X < boxes[j].X + }) + + // Y方向のLISを求める(O(N log N)) + lis := make([]int, 0, len(boxes)) + + for _, box := range boxes { + y := box.Y + idx := lowerBound(lis, y) + if idx == len(lis) { + lis = append(lis, y) + } else { + lis[idx] = y + } + } + + return len(lis) +} + +// lowerBound は slice の中で target 以上の最小のインデックスを返す。 +// @param slice []int - 単調増加列 +// @param target int - 探す値 +// @return int - 最左位置(lower_bound) +func lowerBound(slice []int, target int) int { + left, right := 0, len(slice) + for left < right { + mid := (left + right) / 2 + if slice[mid] < target { + left = mid + 1 + } else { + right = mid + } + } + return left +} + +// main は標準入力を読み込み、最大ネスト数を出力する。 +func main() { + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + n, _ := strconv.Atoi(scanner.Text()) + + boxes := make([]Box, n) + + for i := 0; i < n && scanner.Scan(); i++ { + line := scanner.Text() + parts := strings.Fields(line) + x, _ := strconv.Atoi(parts[0]) + y, _ := strconv.Atoi(parts[1]) + boxes[i] = Box{X: x, Y: y} + } + + result := maxNestingDepth(boxes) + fmt.Println(result) +} + +// ## ✅ 計算量とメモリ使用量 + +// | 項目 | 値 | +// | ------ | ---------------------------------- | +// | 時間計算量 | `O(N log N)`(ソート + LIS) | +// | 空間計算量 | `O(N)`(LIS配列+入力配列) | +// | メモリ使用量 | 約 10〜15MB(N ≒ 100,000 のとき) | +// | 最大入力対応 | `N ≦ 100,000`、`X,Y ≦ 500,000` まで対応 | + +// --- + +// ## ✅ 実行例 + +// ### 入力: + +// ``` +// 5 +// 30 50 +// 10 30 +// 40 10 +// 50 20 +// 40 60 +// ``` + +// ### 出力: + +// ``` +// 3 +// ``` + +// --- + +// ## ✅ 解法ポイント + +// * `sort.Slice()` で X昇順・Y降順の安定ソート +// * `lowerBound` による Y の LIS 構築(ネスト数=LISの長さ) +// * Go のスライスを活かした効率的実装 + diff --git a/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.js b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.js new file mode 100644 index 00000000..49519fea --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.js @@ -0,0 +1,95 @@ +// 以下は、**JavaScript (Node.js 18.16.1)** を使った解法です。 +// 標準入力は `fs` モジュールを使って読み込み、\*\*箱をネストできる最大の深さ(最大チェイン長)\*\*を求めます。 + +// --- + +// ## ✅ 解法概要(O(N log N) 解法) + +// この問題は **2次元のLongest Increasing Subsequence(LIS)問題**に帰着できます。 + +// 1. **X昇順**、**Y降順**でソートする(Xが同じならYが逆順) + +// * こうすることで、`X` も `Y` も単調増加となる部分列を求めるときに、`X` が同じで `Y` だけ違う箱が同列に並ばないようにできます。 +// 2. `Y` だけを取り出し、LIS(Longest Increasing Subsequence)を求める + +// --- + +// ## ✅ コード実装 + +// ```javascript +const fs = require('fs'); + +/** + * 与えられた箱の配列に対して最大で何重にネストできるかを求める + * @param {number[][]} boxes - 各箱の [X, Y] の配列 + * @returns {number} - 最大のネスト数 + */ +function maxNestingDepth(boxes) { + // 1. X昇順、Xが同じならY降順でソート + boxes.sort((a, b) => { + if (a[0] === b[0]) return b[1] - a[1]; // Y降順 + return a[0] - b[0]; // X昇順 + }); + + // 2. Yのみを取り出して LIS を求める + const lis = []; // 単調増加部分列(パイル) + + for (const [_, y] of boxes) { + // 二分探索で挿入位置を探す + let left = 0, right = lis.length; + while (left < right) { + const mid = (left + right) >> 1; + if (lis[mid] < y) left = mid + 1; + else right = mid; + } + + if (left === lis.length) { + lis.push(y); + } else { + lis[left] = y; + } + } + + return lis.length; +} + +// 標準入力の読み取り +function main() { + const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n'); + const N = Number(input[0]); + const boxes = input.slice(1).map(line => line.split(' ').map(Number)); + + const result = maxNestingDepth(boxes); + console.log(result); +} + +main(); +// ``` + +// --- + +// ## ✅ 実行時間・メモリ + +// * 時間計算量: **O(N log N)**(ソートとLISに二分探索) +// * 空間計算量: **O(N)**(LIS保存用) + +// --- + +// ## ✅ テスト例 + +// 入力例1: + +// ``` +// 5 +// 30 50 +// 10 30 +// 40 10 +// 50 20 +// 40 60 +// ``` + +// 出力: + +// ``` +// 3 +// ``` diff --git a/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.php b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.php new file mode 100644 index 00000000..abe6c50e --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.php @@ -0,0 +1,130 @@ + + $boxes - 各箱の [X, Y] を格納した配列 + * @return int - 最大ネスト数 + */ +function maxNestingDepth(array $boxes): int { + // ソート: X 昇順、同じなら Y 降順 + usort($boxes, function ($a, $b) { + if ($a[0] === $b[0]) { + return $b[1] <=> $a[1]; // Y 降順 + } + return $a[0] <=> $b[0]; // X 昇順 + }); + + $lis = []; // 最長増加部分列 (Y方向) + + foreach ($boxes as [$x, $y]) { + $pos = binarySearchLeft($lis, $y); + if ($pos === count($lis)) { + $lis[] = $y; + } else { + $lis[$pos] = $y; + } + } + + return count($lis); +} + +/** + * 二分探索(lower_bound)で挿入位置を探す + * + * @param array $arr - 単調増加配列 + * @param int $target - 挿入対象の数 + * @return int - 挿入すべき位置(最左の >= target の位置) + */ +function binarySearchLeft(array $arr, int $target): int { + $left = 0; + $right = count($arr); + + while ($left < $right) { + $mid = intdiv($left + $right, 2); + if ($arr[$mid] < $target) { + $left = $mid + 1; + } else { + $right = $mid; + } + } + + return $left; +} + +/** + * メイン処理:標準入力から読み込み、最大ネスト数を出力 + */ +function main(): void { + $input = file('php://stdin', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $n = (int)$input[0]; + + $boxes = []; + for ($i = 1; $i <= $n; $i++) { + [$x, $y] = array_map('intval', explode(' ', $input[$i])); + $boxes[] = [$x, $y]; + } + + $result = maxNestingDepth($boxes); + echo $result . PHP_EOL; +} + +main(); +// ``` + +// --- + +// ## ✅ 処理時間・メモリ使用量 + +// | 項目 | 内容 | +// | ------- | ------------------------- | +// | 時間計算量 | `O(N log N)` (ソート + LIS) | +// | 空間計算量 | `O(N)`(LIS用配列 + 入力配列) | +// | メモリ使用量例 | ≒15〜20MB(`N=100,000` の場合) | +// | 対応最大サイズ | `N ≦ 100,000` | + +// --- + +// ## ✅ 入力例 + +// ``` +// 5 +// 30 50 +// 10 30 +// 40 10 +// 50 20 +// 40 60 +// ``` + +// ### 出力: + +// ``` +// 3 +// ``` + +// --- + +// ## ✅ 解説補足 + +// * `usort` による `X` 昇順、`Y` 降順ソートで誤ネストを防止 +// * `binarySearchLeft` による効率的な LIS 構築 +// * PHP 8.2 の型サポート(配列要素のタプル型も docblock で明示) + +// --- + +// ご希望があれば、ネスト順序の復元や可視化解説も可能です。 \ No newline at end of file diff --git a/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.py b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.py new file mode 100644 index 00000000..43b35ea5 --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.py @@ -0,0 +1,102 @@ +# 以下は、Python (CPython 3.11.4) における実装です。 +# 処理時間およびメモリ効率に優れた **O(N log N)** のアルゴリズムを使用し、型ヒント付きで関数分離しています。 + +# --- + +# ## ✅ 解法概要 + +# * ソート:`X` 昇順、`Y` 降順 +# * LIS(Longest Increasing Subsequence):`Y` 方向の最長増加列を求める + +# --- + +# ## 🧠 Python 実装(型ヒント・関数分離付き) + +# ```python +import sys +import bisect +from typing import List, Tuple + +# 最大ネスト数を求める関数 +def max_nesting_depth(boxes: List[Tuple[int, int]]) -> int: + """ + 箱の配列に対して最大ネスト数を計算する。 + + :param boxes: 各箱を (縦, 横) のタプルで表したリスト + :return: 最大ネスト数(整数) + """ + # ソート: X昇順、Xが同じ場合はY降順 + boxes.sort(key=lambda x: (x[0], -x[1])) + + # Yのみ取り出して LIS を求める + lis: List[int] = [] + + for _, y in boxes: + idx = bisect.bisect_left(lis, y) + if idx == len(lis): + lis.append(y) + else: + lis[idx] = y + + return len(lis) + +# メイン関数(標準入力から読み込み) +def main() -> None: + """ + 標準入力から読み込み、最大ネスト数を出力する。 + """ + input = sys.stdin.read().splitlines() + _: int = int(input[0]) + # ✅ 修正後(明示的に2要素に限定) + boxes: List[Tuple[int, int]] = [ (int(x), int(y)) for x, y in (line.split() for line in input[1:]) ] + + result: int = max_nesting_depth(boxes) + print(result) + +if __name__ == "__main__": + main() +# ``` + +# --- + +# ## ✅ 計算量・メモリ使用量 + +# | 項目 | 内容 | +# | --------- | ------------------------------- | +# | 時間計算量 | `O(N log N)` (ソート + LIS) | +# | 空間計算量 | `O(N)` (入力+LIS配列) | +# | 使用メモリ(目安) | 約10〜15MB(N=100,000 時) | +# | 最大入力対応 | `N ≦ 100,000`, `X, Y ≦ 500,000` | + +# --- + +# ## 📘 実行例 + +# ### 標準入力: + +# ``` +# 5 +# 30 50 +# 10 30 +# 40 10 +# 50 20 +# 40 60 +# ``` + +# ### 出力: + +# ``` +# 3 +# ``` + +# --- + +# ## ✅ 解説補足 + +# * `bisect_left` を使って `O(log N)` で LIS の位置を特定 +# * 差し替えながら単調増加列を維持 +# * LIS の長さがネスト可能な最大深さ + +# --- + +# 必要であれば、**LISの復元(実際の箱列)** や、**可視化説明**も可能です。ご希望あればお知らせください。 diff --git a/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.ts b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.ts new file mode 100644 index 00000000..9528a4c5 --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/B24.ts @@ -0,0 +1,101 @@ +// 以下は TypeScript 5.1(Node.js 18.16.1)で書かれた、**箱の最大ネスト数を求める実装**です。 +// `fs` を用いて入力を読み込み、処理時間・メモリ効率を重視した **O(N log N)** の解法を使用しています。 + +// --- + +// ## ✅ TypeScript 実装 + +// ```ts +import * as fs from 'fs'; + +/** + * 与えられた箱の配列に対して最大で何重にネストできるかを求める関数 + * @param boxes - 各箱の [縦, 横] の2次元配列 + * @returns 最大ネスト数(整数) + */ +function maxNestingDepth(boxes: [number, number][]): number { + // 1. X昇順、Xが同じならY降順でソート + boxes.sort((a, b) => { + if (a[0] === b[0]) return b[1] - a[1]; // Y降順 + return a[0] - b[0]; // X昇順 + }); + + // 2. Yの配列でLIS(Longest Increasing Subsequence)を求める + const lis: number[] = []; + + for (const [, y] of boxes) { + let left = 0, right = lis.length; + while (left < right) { + const mid = (left + right) >> 1; + if (lis[mid] < y) { + left = mid + 1; + } else { + right = mid; + } + } + + if (left === lis.length) { + lis.push(y); + } else { + lis[left] = y; + } + } + + return lis.length; +} + +/** + * 標準入力からデータを読み込み、最大ネスト数を出力する関数 + */ +function main(): void { + const input = fs.readFileSync('/dev/stdin', 'utf-8').trim().split('\n'); + const N: number = Number(input[0]); + const boxes: [number, number][] = input.slice(1).map(line => { + const [x, y] = line.split(' ').map(Number); + return [x, y]; + }); + + const result = maxNestingDepth(boxes); + console.log(result); +} + +main(); + +// ## ✅ 実行性能 + +// | 要素 | 内容 | +// | ------- | ----------------------- | +// | 時間計算量 | `O(N log N)`(ソート + LIS) | +// | 空間計算量 | `O(N)`(LIS配列) | +// | メモリ効率 | 高い(最大でも `N` 分の配列) | +// | 最大対応サイズ | `N ≦ 100,000` で十分対応可能 | + +// --- + +// ## ✅ テスト例 + +// ### 入力: + +// ``` +// 5 +// 30 50 +// 10 30 +// 40 10 +// 50 20 +// 40 60 +// ``` + +// ### 出力: + +// ``` +// 3 +// ``` + +// --- + +// ## 🔎 解法補足 + +// * `X` が同じとき `Y` を**降順**に並べる理由: + +// * LIS において `X` が同じで `Y` が昇順だと、誤って同じ `X` によるネストを許してしまう。 +// * 降順にすることで、同じ `X` の箱は先に処理されて除外される(=正しいネスト条件が維持される)。 diff --git a/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/README.md b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/README.md new file mode 100644 index 00000000..75d7290c --- /dev/null +++ b/Algorithm/DynamicProgramming/atCoder/LongestIncreasingSubsequence/B24/README.md @@ -0,0 +1,211 @@ +TypeScript 実装の各処理について、**図解とともに詳細な解析**を行います。 +問題は「2次元の箱ネスト問題」ですが、これは\*\*2次元 LIS(最長増加部分列)\*\*に帰着できます。 + +--- + +## 🧩 問題再掲 + +箱 A が箱 B に入るには: + +``` +A.X < B.X かつ A.Y < B.Y +``` + +回転不可。 +つまり、「縦・横ともに小さい箱は、縦・横ともに大きい箱の中に入れられる」。 + +--- + +## 🧠 アルゴリズムの概要 + +1. `X` 昇順、`Y` 降順にソート +2. `Y` の LIS(最長増加部分列)を求める(Y方向に何個ネストできるか) + +--- + +# 🔢 入力例 + +``` +5 +30 50 +10 30 +40 10 +50 20 +40 60 +``` + +各箱を `(X, Y)` と表記します: + +``` +[30, 50], [10, 30], [40, 10], [50, 20], [40, 60] +``` + +--- + +# 🔧 ステップ①:ソート処理 + +### 条件: + +* `X` 昇順 +* 同じ `X` のときは `Y` 降順 + +```ts +boxes.sort((a, b) => { + if (a[0] === b[0]) return b[1] - a[1]; // Y降順 + return a[0] - b[0]; // X昇順 +}); +``` + +### 📊 ソート結果: + +``` +[10, 30] +[30, 50] +[40, 60] ← Y降順なので先 +[40, 10] +[50, 20] +``` + +### 📌 なぜ Y を降順にする? + +* 同じ X の中で Y が小さい方を後に回すことで、「X が同じ場合に複数の箱がネストされる誤り」を防ぐため。 + +--- + +# 🧮 ステップ②:Y の LIS を求める + +### LISとは? + +* 与えられた数列から「単調増加の部分列」で最大の長さを求める。 +* LISにより「最大ネスト数」が求まる。 + +--- + +## 🎯 対象の Y の配列: + +``` +[30, 50, 60, 10, 20] +``` + +LIS を構築する様子を図解します。 + +### 初期状態: + +``` +lis = [] +``` + +--- + +## ▶️ Y = 30: + +* lis は空 → `30` を push + +``` +lis = [30] +``` + +--- + +## ▶️ Y = 50: + +* `50 > 30` → push + +``` +lis = [30, 50] +``` + +--- + +## ▶️ Y = 60: + +* `60 > 50` → push + +``` +lis = [30, 50, 60] +``` + +--- + +## ▶️ Y = 10: + +* `10 < 30` → 二分探索で差し替え(最初の位置) + +``` +lis = [10, 50, 60] +``` + +--- + +## ▶️ Y = 20: + +* `20 > 10 && < 50` → 2番目に差し替え + +``` +lis = [10, 20, 60] +``` + +--- + +## ✅ 最終 LIS = \[10, 20, 60] + +* 長さは `3` → 最大ネスト数 = **3** + +--- + +# 🧠 二分探索の詳細図 + +例えば `Y = 20` のとき、`lis = [10, 50, 60]` に対して: + +``` +binarySearch(lis, 20) +↓ +mid = 1 → lis[1] = 50 > 20 → right = 1 +mid = 0 → lis[0] = 10 < 20 → left = 1 +→ 差し替え位置 = 1 +``` + +--- + +# 💡 なぜこの方法で正しいのか? + +* LIS により、常に「Y が昇順」なネストを構築する +* Xはソート済なので、Xの条件は保証されている +* Yは単調増加でなければならない → 最長長さを求めることでネスト数を最適化 + +--- + +## 🧾 最終出力 + +```ts +console.log(result); // → 3 +``` + +--- + +# ✅ 最終まとめ(図で確認) + +``` +ソート後の箱: +(10,30) → (30,50) → (40,60) → (40,10) → (50,20) + +対象Y: 30 → 50 → 60 → 10 → 20 +LIS処理: [30] → [30,50] → [30,50,60] → [10,50,60] → [10,20,60] +最長長さ: 3 +``` + +--- + +# 📘 結論 + +* 箱ネスト問題は「2次元 LIS」として解ける +* ソート + 二分探索で `O(N log N)` を達成可能 +* メモリ制約内(最大 `N = 100000`)でも高速・安全に動作 + +| [提出日時](https://atcoder.jp/contests/tessoku-book/submissions/me?desc=true&orderBy=created) | 問題 | ユーザ | 言語 | [得点](https://atcoder.jp/contests/tessoku-book/submissions/me?desc=true&orderBy=score) | [コード長](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=source_length) | 結果 | [実行時間](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=time_consumption) | [メモリ](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=memory_consumption) | | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 2025-07-17 17:16:02 | [B24 - Many Boxes](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_cw) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [Go (go 1.20.6)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5002) | 1000 | 1829 Byte | | 42 ms | 6920 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67661614) | +| 2025-07-17 17:13:24 | [B24 - Many Boxes](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_cw) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [PHP (php 8.2.8)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5016) | 1000 | 1823 Byte | | 199 ms | 56844 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67661547) | +| 2025-07-17 17:11:31 | [B24 - Many Boxes](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_cw) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [Python (CPython 3.11.4)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5055) | 1000 | 1239 Byte | | 188 ms | 41944 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67661509) | +| 2025-07-17 17:02:58 | [B24 - Many Boxes](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_cw) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [TypeScript 5.1 (Node.js 18.16.1)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5058) | 1000 | 1429 Byte | | 214 ms | 81208 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67661269) | +| 2025-07-17 17:00:05 | [B24 - Many Boxes](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_cw) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [JavaScript (Node.js 18.16.1)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5009) | 1000 | 1270 Byte | | 186 ms | 80712 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/67661188) | \ No newline at end of file diff --git "a/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/LetterCombinationsofaPhoneNumber.py" "b/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/LetterCombinationsofaPhoneNumber.py" new file mode 100644 index 00000000..ed8b7a6b --- /dev/null +++ "b/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/LetterCombinationsofaPhoneNumber.py" @@ -0,0 +1,128 @@ +# 以下は、**LeetCode用の Python (CPython 3.11.4)** に準拠した +# 「電話番号の文字列組み合わせ」問題の実装です。 + +# --- + +# ## **実装内容** + +# * **アルゴリズム**:バックトラッキング(DFS探索) +# * **時間計算量**: +# O(3^N × 4^M) + +# * N:3文字キー(2,3,4,5,6,8)の個数 +# * M:4文字キー(7,9)の個数 +# * **空間計算量**: +# O(出力サイズ + 再帰スタック) + +# --- + +# ## **Pythonコード(LeetCode提出形式)** + +# ```python +from typing import List + +class Solution: + def letterCombinations(self, digits: str) -> List[str]: + """ + 与えられた数字文字列から、考えられる全ての文字列の組み合わせを返す + + Parameters + ---------- + digits : str + 電話番号を表す数字文字列 (0 <= len(digits) <= 4) + + Returns + ------- + List[str] + 数字文字列から作成される全ての文字列の組み合わせ + + 時間計算量: O(3^N × 4^M) + 空間計算量: O(組み合わせ数 + 再帰スタック) + """ + if not digits: + return [] + + phone_map: dict[str, str] = { + "2": "abc", "3": "def", "4": "ghi", + "5": "jkl", "6": "mno", "7": "pqrs", + "8": "tuv", "9": "wxyz" + } + + res: List[str] = [] + + def backtrack(index: int, path: str) -> None: + """ + 再帰的に文字列の組み合わせを構築する + + Parameters + ---------- + index : int + digits内の現在の桁位置 + path : str + 現在までの組み合わせ文字列 + """ + # 全桁処理完了時に結果保存 + if index == len(digits): + res.append(path) + return + + # 現在の数字に対応する文字で探索 + for letter in phone_map[digits[index]]: + backtrack(index + 1, path + letter) + + backtrack(0, "") + return res + +# ## **動作例** + +# ```python +# solution = Solution() +# print(solution.letterCombinations("23")) +# # 出力: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'] +# ``` + +# --- + +# ## **解説図(探索木)** + +# ``` +# digits = "23" + +# "" +# / | \ +# "a" "b" "c" +# / \ / \ / \ +# "ad" "ae" "bd" "be" "cd" "ce" +# \ \ \ \ \ \ +# "af" "af" "bf" "bf" "cf" "cf" + +# 出力: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] +# ``` + +# --- + +# ## **時間計算量の詳細** + +# | 項目 | 内容 | +# | -------- | ----------------------- | +# | 1桁の組み合わせ | O(3) or O(4) | +# | 2桁の組み合わせ | O(3×3) = 9 or O(4×3)=12 | +# | 最大4桁 | O(3^N × 4^M) | + +# --- + +# ## **空間計算量** + +# | 項目 | 内容 | +# | ------ | ----------------- | +# | 出力リスト | O(組み合わせ数) | +# | 再帰スタック | O(最大深さ=digitsの長さ) | + +# --- + +# ## **まとめ** + +# * LeetCode提出形式 ✅ +# * 型ヒント ✅ +# * 計算量解析 ✅ +# * 図解による動作説明 ✅ diff --git "a/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/LetterCombinationsofaPhoneNumber.ts" "b/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/LetterCombinationsofaPhoneNumber.ts" new file mode 100644 index 00000000..e46edd67 --- /dev/null +++ "b/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/LetterCombinationsofaPhoneNumber.ts" @@ -0,0 +1,82 @@ +// ローカル実行や関数のみで使いたい場合は、**グローバル関数で実装**できます。 +// LeetCodeの制約を無視して「関数版」のコードを提供します。 + +// --- + +// ## **TypeScript 関数実装(クラスなし)** + +// ```typescript +/** + * 与えられた数字文字列から、考えられる全ての文字列の組み合わせを返す + * @param digits - 数字文字列(0 <= digits.length <= 4) + * @returns 可能な文字列組み合わせの配列 + * + * 時間計算量: O(3^N × 4^M) + * 空間計算量: O(出力サイズ + 再帰スタック) + */ +function letterCombinations(digits: string): string[] { + if (digits.length === 0) return []; + + const phoneMap: Record = { + '2': "abc", '3': "def", '4': "ghi", + '5': "jkl", '6': "mno", '7': "pqrs", + '8': "tuv", '9': "wxyz" + }; + + const result: string[] = []; + + function backtrack(index: number, path: string): void { + if (index === digits.length) { + result.push(path); + return; + } + + const currentDigit = digits[index]; + const letters = phoneMap[currentDigit]; + + for (const letter of letters) { + backtrack(index + 1, path + letter); + } + } + + backtrack(0, ""); + return result; +} +// ``` + +// --- + +// ## **使い方(ローカル実行用)** + +// ```typescript +// console.log(letterCombinations("23")); +// // 出力例: ["ad","ae","af","bd","be","bf","cd","ce","cf"] + +// console.log(letterCombinations("")); +// // 出力: [] + +// console.log(letterCombinations("2")); +// // 出力: ["a","b","c"] +// ``` + +// --- + +// ## **計算量** + +// | 項目 | 内容 | +// | ----- | ------------------------------------------------------ | +// | 時間計算量 | O(3^N × 4^M)(N: '2', '3', ..., '8' の個数, M: '7'と'9'の個数) | +// | 空間計算量 | O(出力サイズ + 再帰スタック) | + +// --- + +// ## **用途に応じた使い分け** + +// | 用途 | 実装パターン | +// | --------------- | --------------------------------- | +// | LeetCode提出 | `class Solution` 必須 | +// | Node.js / 関数テスト | `function letterCombinations` でOK | + +// --- + +// もし、**Node.jsでの実行コマンド(`ts-node`)や、最適化版実装**も必要でしたら教えてください! diff --git "a/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/README.md" "b/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/README.md" new file mode 100644 index 00000000..614cce61 --- /dev/null +++ "b/DataStructures/Trees/BFS\343\203\273DFS/leetcode/17. Letter Combinations of a Phone Number/README.md" @@ -0,0 +1,136 @@ +以下は **`letterCombinations` 関数の具体的な処理を図解しながら詳しく解説**します。 + +--- + +## **問題概要** + +### 入力: + +数字列 `"23"` + +### 電話キー対応: + +| 数字 | 対応文字 | +| -- | ------- | +| 2 | a, b, c | +| 3 | d, e, f | + +--- + +## **再帰(バックトラッキング)の動作イメージ** + +### 入力:`digits = "23"` + +以下のように、**木構造で全探索**しています。 + +``` + "" + / | \ + "a" "b" "c" + / \ / \ / \ + "ad" "ae" "bd" "be" "cd" "ce" + | | | | | | + "af" "af" "bf" "bf" "cf" "cf" +``` + +--- + +### **具体的な処理フロー** + +1. **初期呼び出し** + +```typescript +backtrack(0, "") +``` + +* index: 0(最初の文字) +* path: ""(現在の文字列は空) + +--- + +2. **1段目(digits\[0] = '2')** + +```typescript +letters = "abc" +``` + +* "a" を選択 → 次の桁へ +* "b" を選択 → 次の桁へ +* "c" を選択 → 次の桁へ + +--- + +3. **2段目(digits\[1] = '3')** + +```typescript +letters = "def" +``` + +各パターンごとに以下を繰り返す: + +| 文字列 | 動作 | +| --------- | ------- | +| "a" + "d" | "ad" 完成 | +| "a" + "e" | "ae" 完成 | +| "a" + "f" | "af" 完成 | +| "b" + "d" | "bd" 完成 | +| "b" + "e" | "be" 完成 | +| "b" + "f" | "bf" 完成 | +| "c" + "d" | "cd" 完成 | +| "c" + "e" | "ce" 完成 | +| "c" + "f" | "cf" 完成 | + +--- + +## **全体の組み合わせ図** + +``` +2: [a, b, c] +3: [d, e, f] + +全組み合わせ: +[a] -> [d, e, f] => "ad", "ae", "af" +[b] -> [d, e, f] => "bd", "be", "bf" +[c] -> [d, e, f] => "cd", "ce", "cf" +``` + +--- + +## **コード処理ごとの解説** + +| 処理 | 内容 | 図解 | +| ------------------------- | ------------ | ------------------------- | +| `backtrack(0, "")` | 探索開始 | `""` | +| `path + letter` | 部分文字列を構築 | 例:"a"、"b"、"c" | +| `index === digits.length` | 終端判定、組み合わせ確定 | "ad"など | +| `result.push(path)` | 結果保存 | `["ad", "ae", "af", ...]` | + +--- + +## **時間・空間計算量** + +| 項目 | 内容 | +| ----- | -------------------------------------------------------------- | +| 時間計算量 | O(3^N × 4^M)
N:3文字のキー(2,3,4,5,6,8)個数
M:4文字のキー(7,9)個数 | +| 空間計算量 | O(組み合わせ数 + 再帰スタック) | + +--- + +## **まとめ図(最終結果)** + +``` +Input: "23" + +出力: +["ad", "ae", "af", + "bd", "be", "bf", + "cd", "ce", "cf"] +``` + +--- + +## **視覚化まとめ** + +* **木構造探索(バックトラッキング)** +* **全探索(DFS)で全組み合わせ列挙** +* **3文字×3文字 → 9パターン出力**