From 792ecdafaf584b979e915793d6fd0ec50482ee3e Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Wed, 5 Nov 2025 14:23:08 +0900 Subject: [PATCH 1/2] Data Structures: leetcode 1. Two Sum Map --- DataStructures/Map/leetcode/claude/README.md | 271 ++++ .../Map/leetcode/claude/README_react.html | 1418 +++++++++++++++++ DataStructures/Map/leetcode/gpt/README.md | 182 +++ .../Map/leetcode/gpt/Two_Sum_js.ipynb | 239 +++ .../Map/leetcode/gpt/Two_Sum_py.ipynb | 243 +++ .../Map/leetcode/gpt/Two_Sum_ts.ipynb | 237 +++ 6 files changed, 2590 insertions(+) create mode 100644 DataStructures/Map/leetcode/claude/README.md create mode 100644 DataStructures/Map/leetcode/claude/README_react.html create mode 100644 DataStructures/Map/leetcode/gpt/README.md create mode 100644 DataStructures/Map/leetcode/gpt/Two_Sum_js.ipynb create mode 100644 DataStructures/Map/leetcode/gpt/Two_Sum_py.ipynb create mode 100644 DataStructures/Map/leetcode/gpt/Two_Sum_ts.ipynb diff --git a/DataStructures/Map/leetcode/claude/README.md b/DataStructures/Map/leetcode/claude/README.md new file mode 100644 index 00000000..a73241c8 --- /dev/null +++ b/DataStructures/Map/leetcode/claude/README.md @@ -0,0 +1,271 @@ +# Two Sum - ハッシュテーブル1パス探索 + +

目次

+ +- [概要](#overview) +- [アルゴリズム要点(TL;DR)](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python実装](#impl) +- [CPython最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +

概要

+ +**問題**: 整数配列 `nums` と整数 `target` が与えられたとき、和が `target` になる2要素の**添字ペア**を返す。 + +**要件**: + +- 解は必ず1つ存在する(一意性保証) +- 同じ要素を2回使用してはならない +- 添字の順序は任意 + +**制約**: + +- `2 <= len(nums) <= 10^4` +- `-10^9 <= nums[i], target <= 10^9` + +**Follow-up**: O(n²)未満の時間計算量で解けるか? + +--- + +

アルゴリズム要点(TL;DR)

+ +- **戦略**: ハッシュテーブル(`dict`)を用いた**1パス探索** +- **データ構造**: `dict[int, int]` で 値→添字 を管理 +- **時間計算量**: **O(n)** (配列を1回走査、各要素で平均O(1)のハッシュ操作) +- **空間計算量**: **O(n)** (最悪ケースで n-1 個のエントリを保持) +- **最適化**: 補数(`target - x`)の事前照会により、見つかった瞬間に即座に返却 + +--- + +

図解

+ +## フローチャート + +```mermaid +flowchart TD + Start[Start twoSum] --> Init[Initialize empty dict seen] + Init --> Loop{Enumerate nums} + Loop -- For each i,x --> Calc[Compute need = target - x] + Calc --> Check{Is need in seen?} + Check -- Yes --> Found[Return seen_need, i] + Check -- No --> Store{Is x in seen?} + Store -- No --> Add[seen_x = i] + Store -- Yes --> Skip[Skip duplicate] + Add --> Loop + Skip --> Loop + Loop -- End of array --> Unreachable[Return -1,-1] + Found --> End[End] + Unreachable --> End +``` + +**説明**: 配列を左から1回走査し、各要素 `x` に対して補数 `need = target - x` がハッシュに既存か確認。存在すれば即座にペアを返却。存在しなければ `x` を辞書に登録して次へ進む。 + +### データフロー図 + +```mermaid +graph LR + subgraph Input + A[nums array] --> B[target value] + end + subgraph Core_Logic + B --> C[Enumerate with index] + C --> D[Compute complement] + D --> E[Hash lookup in seen] + E -- Hit --> F[Return indices] + E -- Miss --> G[Register current value] + end + G --> C + F --> H[Output pair] +``` + +**説明**: 入力配列を走査しながら、各要素の補数をハッシュテーブルで照会。ヒット時は添字ペアを出力、ミス時は現在値を登録して次の要素へ遷移。 + +--- + +

正しさのスケッチ

+ +**不変条件**: + +- ループの各ステップで、辞書 `seen` は「現在位置より左側の要素のうち、初出のもの」の値→添字マッピングを保持 + +**網羅性**: + +- 解が必ず1つ存在するため、ペア `(i, j)` (i < j) のうち `j` に到達した時点で `nums[i]` は辞書に登録済み +- 補数 `need = target - nums[j]` が `nums[i]` に一致するため、`need in seen` で検出される + +**基底条件**: + +- 配列長 >= 2、解の一意性保証により、ループ終了前に必ず `return` が実行される + +**終了性**: + +- 配列を1回走査するため、最悪でも O(n) ステップで終了 + +--- + +

計算量

+ +| 指標 | 計算量 | 理由 | +|------------|---------|------------------------------------------| +| **時間** | **O(n)** | 配列を1パス、各要素でハッシュ操作(平均O(1)) | +| **空間** | **O(n)** | 最悪ケースで n-1 個の要素を辞書に格納 | + +**比較**: 二重ループ(O(n²))やソート+2ポインタ(O(n log n))より高速。 + +--- + +

Python実装

+ +```python +from __future__ import annotations +from typing import List + + +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + """ + ハッシュテーブル1パスでTwo Sumを解く + + Args: + nums: 整数配列(長さ >= 2) + target: 目標和 + + Returns: + 和が target になる2要素の添字リスト [i, j] + + Time Complexity: O(n) + Space Complexity: O(n) + """ + seen: dict[int, int] = {} # value -> first occurrence index + + for i, x in enumerate(nums): + need = target - x + + # 補数が既出か確認 + if need in seen: + return [seen[need], i] + + # 現在値を初出のみ登録(重複時は最左を保持) + if x not in seen: + seen[x] = i + + # 問題前提(解が必ず存在)により到達しないが型整合のため + return [-1, -1] +``` + +**主要ステップ**: + +1. **初期化**: 空の辞書 `seen` を用意 +2. **走査**: `enumerate` でインデックスと値を同時取得 +3. **補数計算**: `need = target - x` +4. **照会**: `need in seen` で既出チェック → ヒット時は即返却 +5. **登録**: ミス時は `x` を辞書に追加(初出のみ) + +--- + +

CPython最適化ポイント

+ +### 標準実装の最適化 + +1. **`enumerate` の活用** + - Pythonレベルでのインデックス管理を回避 + - Cレベルのイテレータで高速化 + +2. **辞書のC実装** + - CPythonの `dict` はハッシュテーブルのC実装で平均O(1) + - `in` 演算子による存在確認も高速 + +3. **ローカル変数束縛** + - ループ内で `target` や `seen` を参照するがグローバル/属性アクセスは不要 + +### マイクロ最適化版(上級者向け) + +```python +from typing import List + + +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + """ + メソッド束縛による属性解決削減版 + Time: O(n), Space: O(n) + """ + seen: dict[int, int] = {} + # 辞書メソッドをローカル束縛(属性解決オーバーヘッド削減) + contains = seen.__contains__ + getitem = seen.__getitem__ + setitem = seen.__setitem__ + + for i, x in enumerate(nums): + if contains(x): + return [getitem(x), i] + setitem(target - x, i) + + return [-1, -1] +``` + +**改善点**: + +- `__contains__` / `__getitem__` / `__setitem__` をローカルで保持 +- 属性解決(`.` 演算子)のバイトコードを削減 +- 補数を辞書のキーとして登録する方式で `need` 変数を削減 + +**注意**: 効果は数%程度。LeetCode実行環境のノイズも大きいため、複数回実行して判断。 + +--- + +

エッジケースと検証観点

+ +| ケース | 入力例 | 期待出力 | 検証観点 | +|--------------------|-----------------------------|-----------|------------------------| +| **最小長(2要素)** | `[3, 3], 6` | `[0, 1]` | 同値ペアで正常動作 | +| **負数混在** | `[-1, -2, -3, -4, -5], -8` | `[2, 4]` | 負数でもハッシュ探索が成立 | +| **大きな値** | `[10^9, 10^9-1], 2*10^9-1` | `[0, 1]` | 整数オーバーフローなし | +| **重複値が複数** | `[2, 5, 5, 11], 10` | `[1, 2]` | 最左のペアを優先(初出保持) | +| **解が配列末尾** | `[1, 2, 3, 4], 7` | `[2, 3]` | 全走査で最後まで正しく動作 | +| **0を含む** | `[0, 4, 3, 0], 0` | `[0, 3]` | 0の扱いでバグらない | + +**境界値**: + +- `len(nums) = 2` (最小長) +- `nums[i] = -10^9, 10^9` (範囲端) +- `target = -10^9, 10^9` + +**型チェック(pylance互換)**: + +- `nums: List[int]` 明示 +- 戻り値も `List[int]` 固定 + +--- + +

FAQ

+ +### Q1: なぜソート+2ポインタではなくハッシュなのか? + +**A**: ソートは O(n log n) で、さらに元の添字を保持するため `(値, 添字)` ペアを生成する必要がある。ハッシュは O(n) で、添字管理も自然に行える。 + +### Q2: 同じ要素を2回使ってしまう心配は? + +**A**: ループで「先に照会→後で登録」の順序を守るため、現在の `x` は辞書に未登録。補数が `x` 自身でも、別の位置の `x` のみがヒットする。 + +### Q3: 重複値が複数ある場合の挙動は? + +**A**: `if x not in seen` により、最初の出現のみ辞書に保持。後続の同値は登録しないため、最左の添字が優先される。 + +### Q4: LeetCodeで実行時間にばらつきがあるのはなぜ? + +**A**: サーバー負荷やキャッシュ状態の影響。数回提出して中央値で判断するのが妥当。マイクロ最適化の効果は数%程度。 + +### Q5: 業務で使う場合の改良点は? + +**A**: 入力検証(型チェック、長さチェック)を追加し、`TypeError` / `ValueError` を明示的に送出。ドキュメント文字列も詳細化。 + +--- + +**まとめ**: Two Sum は**ハッシュテーブル1パス**が最適解。CPythonの `dict` のC実装を活かし、O(n)時間・O(n)空間で安定動作。Follow-upの O(n²) 未満も満たし、型安全性・可読性も両立。 diff --git a/DataStructures/Map/leetcode/claude/README_react.html b/DataStructures/Map/leetcode/claude/README_react.html new file mode 100644 index 00000000..d14c3a90 --- /dev/null +++ b/DataStructures/Map/leetcode/claude/README_react.html @@ -0,0 +1,1418 @@ + + + + + + Two Sum - ハッシュテーブル1パス探索 + + + + + + + + + + + + + + + + + + + + + +
+

+ アルゴリズム概要 +

+ +

+ 問題:整数配列 + nums と整数 + target が与えられたとき、和が + target + になる2要素の添字ペアを返す。 +

+ +
+

要件:

+
    +
  • 解は必ず1つ存在する(一意性保証)
  • +
  • 同じ要素を2回使用してはならない
  • +
  • 添字の順序は任意
  • +
+
+ +
+

入出力例:

+
Input: nums = [2,7,11,15], target = 9
+Output: [0,1]
+説明: nums[0] + nums[1] = 2 + 7 = 9
+
+Input: nums = [3,2,4], target = 6
+Output: [1,2]
+
+Input: nums = [3,3], target = 6
+Output: [0,1]
+
+ +
+

戦略:

+
    +
  • ハッシュテーブル(dict)を用いた1パス探索
  • +
  • + 各要素 x に対し、補数 + need = target - x + が既出かを確認 +
  • +
  • 見つかった時点で即座に返却 → O(n) 時間
  • +
  • 最悪ケースで n-1 個のエントリを保持 → O(n) 空間
  • +
+
+
+ + +
+

+ ステップバイステップ解説 +

+ +
+
+ + +
+

+ Python実装 +

+ +
from typing import List
+
+
+class Solution:
+    def twoSum(self, nums: List[int], target: int) -> List[int]:
+        """
+        ハッシュテーブル1パスでTwo Sumを解く
+
+        Args:
+            nums: 整数配列(長さ >= 2)
+            target: 目標和
+
+        Returns:
+            和が target になる2要素の添字リスト [i, j]
+
+        Time Complexity: O(n)
+        Space Complexity: O(n)
+        """
+        seen: dict[int, int] = {}  # value -> first occurrence index
+
+        for i, x in enumerate(nums):
+            need = target - x
+
+            # 補数が既出か確認
+            if need in seen:
+                return [seen[need], i]
+
+            # 現在値を初出のみ登録(重複時は最左を保持)
+            if x not in seen:
+                seen[x] = i
+
+        # 問題前提(解が必ず存在)により到達しないが型整合のため
+        return [-1, -1]
+
+ + +
+

+ フローチャート +

+ +
+ + + + + + + + + + + + + + + + + + + + 開始 twoSum + + + + + + + seen = {} を初期化 + + + + + + + 配列を走査 + 各 i, x について + + + + + + 要素あり + + + + need = target - x を計算 + + + + + + + need が + seen にあるか? + + + + + + はい + + + + [seen[need], i] + を返却 + + + + + + + いいえ + + + + x が + seen にあるか? + + + + + + いいえ + + + + + seen[x] = i を登録 + + + + + + はい + + + + スキップ + + + + + + + + 次の要素へ + + + + + + 配列終了 + + + + [-1, -1] を返却 + + + + + + + 終了 + + +
+ +

+ フローの説明:
+ 1. 空の辞書 seen を初期化
+ 2. 配列を左から順に走査(各要素を + x、添字を + i とする)
+ 3. 補数 + need = target - x を計算
+ 4. need が辞書に存在するか確認 → + 存在すれば即座に + [seen[need], i] を返却
+ 5. 存在しなければ、x + が辞書に未登録なら + seen[x] = i を登録
+ 6. 次の要素へ進む(ループバック)
+ 7. 全要素を処理しても解が見つからなければ終了(問題前提では到達しない) +

+
+ + +
+

+ 計算量分析 +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 指標 + + 本実装(ハッシュ1パス) + + 二重ループ + + ソート+二ポインタ +
+ 時間計算量 + + O(n) + O(n²)O(n log n)
+ 空間計算量 + + O(n) + O(1)O(n)
+ 実装コスト + + 低 +
+ Follow-up対応 + + ✅ O(n²)未満 + ❌ O(n²)✅ O(n log n)
+
+ +
+

詳細分析:

+
    +
  • + 時間 O(n): + 配列を1回走査(n回)、各ステップでハッシュ操作(平均O(1)) +
  • +
  • 空間 O(n): 最悪ケースで n-1 個の要素を辞書に保持
  • +
  • + 最適性: + 問題の性質上、全要素を少なくとも1回は確認する必要があるため O(n) + が理論的下限 +
  • +
  • + CPython特性: dict はC実装で高速、enumerate + も効率的なイテレータ +
  • +
+
+
+ + + + + + + + + + + + + + + + diff --git a/DataStructures/Map/leetcode/gpt/README.md b/DataStructures/Map/leetcode/gpt/README.md new file mode 100644 index 00000000..625b8180 --- /dev/null +++ b/DataStructures/Map/leetcode/gpt/README.md @@ -0,0 +1,182 @@ +# Two Sum - 一発探索の定番ハッシュ + +## Table of Contents + +* [概要](#overview) +* [アルゴリズム要点(TL;DR)](#tldr) +* [図解](#figures) +* [正しさのスケッチ](#correctness) +* [計算量](#complexity) +* [Python 実装](#impl) +* [CPython 最適化ポイント](#cpython) +* [エッジケースと検証観点](#edgecases) +* [FAQ](#faq) + +

概要

+ +* **プラットフォーム/ID**: LeetCode 1 +* **問題タイトル**: Two Sum +* **要約**: 整数配列 `nums` と整数 `target` が与えられる。**和が `target` になる 2 要素のインデックス**を任意順で返す。 +**各入力には解が一意に存在**し、**同じ要素を 2 度使えない**。 +* **入出力仕様(簡潔)** + + * 入力: `nums: List[int]`, `target: int` + * 出力: `List[int]`(長さ 2 のインデックス配列) +* **想定データ構造**: Array(Python の `list`) +* **代表例** + + * `nums=[2,7,11,15], target=9 -> [0,1]` + * `nums=[3,2,4], target=6 -> [1,2]` + * `nums=[3,3], target=6 -> [0,1]` +* **関数シグネチャ(LeetCode準拠)** + + * `class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]:` +* **制約要点** + + * `2 <= len(nums) <= 10^4` + * `-10^9 <= nums[i], target <= 10^9` + * **解は必ず存在し一意** + +

アルゴリズム要点(TL;DR)

+ +* **戦略**: 1 パスで走査し、**値→最初の添字**を `dict` に記録。各要素 `x` で **補数 `target-x` が既出か**を O(1) 期待値で照会。 +* **データ構造**: `dict[int, int]`(ハッシュテーブル、C 実装で高速)。 +* **計算量**: **Time O(n)** / **Space O(n)**。 +* **実装の肝**: 「先に照会 → 後で登録」。これで同じ要素を 2 回使うことを防ぎ、二重カウントを避ける。 +* **安定性**: 一意解前提のため、見つかり次第即 return でよい。 + +

図解

+ +### **フローチャート** + +```mermaid +flowchart TD + Start[Start twoSum] + Start --> Init[Init dict seen value to index] + Init --> Loop{More items} + Loop -- No --> Fail[Return no answer or sentinel] + Loop -- Yes --> Read[Read x and index i] + Read --> Need[Compute need equal target minus x] + Need --> Check{need in seen} + Check -- Yes --> Ret[Return pair seen need and i] + Check -- No --> Store[Store x to seen with index i] + Store --> Loop +``` + +*説明*: 配列を 1 回走査し、補数が辞書にあればペアを返す。なければ現在値を辞書へ保存。 + +### **データフロー図** + +```mermaid +graph LR + A[nums] --> B[for enumerate] + B --> C[compute need] + C --> D[dict lookup] + D --> E[found pair] + D --> F[store value index] + E --> G[output indices] + F --> B +``` + +*説明*: `need` 計算 → ハッシュ照会 → 見つかれば出力、無ければ記録して次へ。 + +

正しさのスケッチ

+ +* **不変条件**: ループ時点までに見た全要素 `nums[0..i-1]` の**最初の添字**が `seen[value]` に保存されている。 +* **網羅性**: 任意の解 `(p, q)`(`p < q`)について、`q` に到達した時点で `p` は既に `seen[nums[p]] = p` として保存済み。 +よって `need = target - nums[q] = nums[p]` が `seen` に存在し、返却される。 +* **一意性**: 問題が一意解を保証。最初に見つかったペアを返せばよい。 +* **終了性**: 配列は有限長。各ステップは O(1) で進み、最大 `n` ステップで終了。 +* **同一要素を二度使わない**: 「先に照会 → 後で保存」の順序により、`need == x` の場合でも現在の `i` は辞書未登録。つまり常に異なるインデックスのペアを生成。 + +

計算量

+ +* **時間計算量**: **O(n)**(各要素につき定数回のハッシュ照会・代入)。 +* **空間計算量**: **O(n)**(最悪で `n-1` 個の値を保存)。 + +| 実装方針 | 時間 | 空間 | 備考 | +| --------- | ---------- | -------- | -------------- | +| ハッシュ1パス | **O(n)** | **O(n)** | 最適。辞書は C 実装で高速 | +| ソート+二ポインタ | O(n log n) | O(n) | 元インデックス復元が必要 | +| 二重ループ | O(n²) | O(1) | 小規模のみ妥当 | + +

Python 実装

+ +> LeetCode 用の **class 形式**。`pylance` で型エラーが出ないよう型注釈を付与。実装は **Pure**(外部副作用なし)。 + +```python +from __future__ import annotations +from typing import List + + +class Solution: + """ + Two Sum - value to index dict, single pass. + + 方針: + - ループ中は「補数が既出か」を先に確認し、なければ現在値を記録。 + - seen は「値 -> 最初の添字」を保持する。 + """ + + def twoSum(self, nums: List[int], target: int) -> List[int]: + """ + ハッシュ1パス実装 + Args: + nums: 整数配列 + target: 目標和 + Returns: + 条件を満たす 2 つのインデックス(任意順) + Time: + O(n) + Space: + O(n) + """ + seen: dict[int, int] = {} # value -> first index + # enumerate で Python レベルのインデックス管理コストを削減 + for i, x in enumerate(nums): + need = target - x + j = seen.get(need) + if j is not None: + # 一意解保証のため見つかり次第返す + return [j, i] + # 先に照会してから保存。重複値は最初の位置を固定化 + if x not in seen: + seen[x] = i + # 問題の前提では到達しないが、型整合のため固定値を返す + return [-1, -1] +``` + +

CPython 最適化ポイント

+ +* **ハッシュ活用**: `dict` は C 実装のオープンアドレッシングで **平均 O(1)**。 +* **属性アクセス削減**: さらに突き詰めるなら `contains = seen.__contains__` など**ローカル束縛**で属性解決コストを削減(マイクロ最適化)。 +* **ループ形**: `for i, x in enumerate(nums)` は Python レベルのインクリメントと添字取得をまとめて実行、可読性と性能のバランスが良い。 +* **一時オブジェクトの抑制**: 補助コンテナやスライスを作らない。タプル生成などの無駄を避ける。 +* **例外ベース回避**: `try/except KeyError` ではなく `get` や `in` を使用し、**例外の高コストパス**を避ける。 + +

エッジケースと検証観点

+ +* **最小長**: `len(nums) == 2`(直ちに 2 要素で判定)。 +* **重複値**: `nums = [3,3], target=6`(同じ値の 2 つを正しく扱う)。 +* **負数・ゼロ**: `nums = [-1, -2, -3, -4, -5], target=-8`、`nums=[0,4,3,0], target=0`。 +* **大きい値**: `±10^9` 付近でもオーバーフローを起こさない(Python は任意精度 int)。 +* **解が配列の後半で確定**: `p < q` の形で `q` 到達時に `p` が辞書にあることを確認。 +* **解なし入力**: 問題前提では発生しないが、堅牢実装では例外送出・番兵値返却の方針を決める。 + +

FAQ

+ +* **Q. なぜ二ポインタではなくハッシュなのか?** + A. 二ポインタはソートが必要で **O(n log n)**。元インデックスの復元も必要。一方ハッシュは **O(n)** で一意解を即返せる。 + +* **Q. 辞書に保存する値は「現在値」か「補数」か?** + A. どちらでも正しく実装できる。ここでは **値→最初の添字** を保存し、`need in seen` を照会する形を採用(読みやすく定番)。 + マイクロ最適化として「**補数→添字** を保存して `x in seen` を照会」する等価手もある。 + +* **Q. 同じ要素を二度使うバグは起きない?** + A. 先に照会してから保存する順序により、`need == x` のケースでも**現在の i は未登録**であり、常に **異なるインデックス**のペアを返す。 + +* **Q. 早期終了は常に正しい?** + A. 解は一意で必ず存在するため、最初に見つかった時点で終了して正しい。 + +* **Q. Python の int でオーバーフローは?** + A. Python の `int` は任意精度。範囲制約は問題側のみで、計算上のオーバーフローは起きない。 diff --git a/DataStructures/Map/leetcode/gpt/Two_Sum_js.ipynb b/DataStructures/Map/leetcode/gpt/Two_Sum_js.ipynb new file mode 100644 index 00000000..93124eb1 --- /dev/null +++ b/DataStructures/Map/leetcode/gpt/Two_Sum_js.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "930de1d6", + "metadata": {}, + "source": [ + "# 1. 問題の分析\n", + "\n", + "## 競技プログラミング視点\n", + "\n", + "* **最速**はハッシュ(値→添字)の1パス探索。各要素 `x` に対し、補数 `target-x` を先に見たか即時判定で **O(n)**。\n", + "* **一意解**が保証されるため、見つかったら即 return 可。安定した定数因子で高速。\n", + "\n", + "## 業務開発視点\n", + "\n", + "* 入力検証を入れて異常系を早期検知(型・長さ・値域)。\n", + "* 命名は `seen`(value→index Map)を使用。例外はテスト容易性のため **TypeError / RangeError** を明確化。\n", + "\n", + "## JavaScript特有の考慮点\n", + "\n", + "* **V8 最適化**: 数値単型配列前提・`for` ループ・`Map` を再利用し hidden class を安定化。\n", + "* **GC対策**: ループ内で不要オブジェクト生成をしない。`Map#get` 結果を一時変数に保持。\n", + "* **配列操作特性**: `for` インデックス走査が最も安定。`forEach` はコールバック割当の分だけ不利。\n", + "\n", + "---\n", + "\n", + "# 2. アルゴリズムアプローチ比較\n", + "\n", + "| アプローチ | 時間計算量 | 空間計算量 | JS実装コスト | 可読性 | 備考 |\n", + "| --------------------- | ---------- | ----- | ------- | --- | ----------------------- |\n", + "| 方法A(ハッシュ1パス) | **O(n)** | O(n) | 低 | 中 | 値→添字 `Map`。一意解即時 return |\n", + "| 方法B(ソート+二分/二-pointer) | O(n log n) | O(n) | 中 | 高 | 元添字保持のためペア配列作成が必要 |\n", + "| 方法C(二重ループ) | O(n²) | O(1) | 低 | 高 | 小規模のみ妥当、Follow-up 非充足 |\n", + "\n", + "---\n", + "\n", + "# 3. 選択したアルゴリズムと理由\n", + "\n", + "* **選択**: 方法A(ハッシュ1パス)\n", + "* **理由**: Follow-up の「O(n²) より良く」を満たし、実装が簡潔で定数因子も小さい\n", + "* **JS最適化ポイント**:\n", + "\n", + " * `for (let i = 0; i < n; i++)` の単純走査\n", + " * ループ内は `Map#get` → 比較 → `Map#set` の固定順\n", + " * 一時配列/オブジェクト生成を抑制、例外はホットパス外(冒頭)で判定\n", + "\n", + "---\n", + "\n", + "# 4. コード実装(solution.js|LeetCode 形式)\n", + "\n", + "* 実行環境: **JavaScript (Node.js v22.14.0)**\n", + "* Module: **CommonJS**\n", + "* 外部ライブラリ: **不使用(Node標準のみ)**\n", + "* 実行: `node solution.js`(ローカル検証用に `module.exports` も付与。LeetCode では無視されます)\n", + "\n", + "```javascript\n", + "'use strict';\n", + "\n", + "/**\n", + " * Two Sum(Pure Function)\n", + " * 値→添字を保持する1パスのハッシュ法で解く。\n", + " *\n", + " * @param {number[]} nums - 整数配列(単型 number 前提)\n", + " * @param {number} target - 目標和\n", + " * @returns {[number, number]} - 条件を満たす 2 要素の添字(任意順)\n", + " * @throws {TypeError} 配列/数値型でない、または有限数でない場合\n", + " * @throws {RangeError} 配列長や値域が制約外、あるいは解が存在しない場合\n", + " * @complexity 時間 O(n), 空間 O(n)\n", + " */\n", + "var twoSum = function (nums, target) {\n", + " // --- 入力検証(軽量 & 早期) ---\n", + " if (!Array.isArray(nums)) throw new TypeError('nums must be an array');\n", + " if (typeof target !== 'number' || !Number.isFinite(target)) {\n", + " throw new TypeError('target must be a finite number');\n", + " }\n", + " const n = nums.length;\n", + " // 問題制約: 2 <= n <= 1e4\n", + " if (n < 2 || n > 1e4) throw new RangeError('nums length is out of range [2, 1e4]');\n", + "\n", + " // 値域チェック(-1e9..1e9)\n", + " for (let i = 0; i < n; i++) {\n", + " const v = nums[i];\n", + " if (typeof v !== 'number' || !Number.isFinite(v)) {\n", + " throw new TypeError('nums must contain only finite numbers');\n", + " }\n", + " if (v < -1e9 || v > 1e9) {\n", + " throw new RangeError('nums[i] is out of allowed range [-1e9, 1e9]');\n", + " }\n", + " }\n", + " if (target < -1e9 || target > 1e9) {\n", + " // 厳密には問題は target にも値域があるため\n", + " throw new RangeError('target is out of allowed range [-1e9, 1e9]');\n", + " }\n", + "\n", + " // --- 本処理:ハッシュ1パス ---\n", + " // seen: 値 -> その値を最初に見つけた添字\n", + " /** @type {Map} */\n", + " const seen = new Map();\n", + "\n", + " for (let i = 0; i < n; i++) {\n", + " const x = nums[i];\n", + " const need = target - x;\n", + " const j = seen.get(need);\n", + " // Map#get は存在しない場合 undefined を返す\n", + " if (j !== undefined) {\n", + " // 一意解前提につき即 return(添字の順不同)\n", + " return [j, i];\n", + " }\n", + " // 同じ要素を2回使わないため、未登録時のみ格納(最初の添字を固定化)\n", + " if (!seen.has(x)) seen.set(x, i);\n", + " }\n", + "\n", + " // 問題文上は常に解が存在するが、検証用途に例外を残す\n", + " throw new RangeError('No valid two-sum solution found');\n", + "};\n", + "\n", + "// --- ローカル実行サポート(LeetCode では無視される) ---\n", + "module.exports = twoSum;\n", + "\n", + "// // 参考:ローカルでの簡易確認(コメント解除して利用)\n", + "// // console.log(twoSum([2,7,11,15], 9)); // [0,1]\n", + "// // console.log(twoSum([3,2,4], 6)); // [1,2]\n", + "// // console.log(twoSum([3,3], 6)); // [0,1]\n", + "\n", + "Analyze Complexity\n", + "Runtime 1 ms\n", + "Beats 87.91%\n", + "Memory 56.86 MB\n", + "Beats 41.70%\n", + "\n", + "```\n", + "---\n", + "\n", + "# 5. 追加メモ(JS最適化チェックリスト)\n", + "\n", + "* ループは **`for` 基本**。`forEach/map` は不要なコールバック割当で遅くなる。\n", + "* `Map` は数値キーに強く、`Object` よりキー変換コストが少ない(文字列化不要)。\n", + "* 例外はホットパス外(冒頭の検証)に寄せる。\n", + "* 配列は **単型(number のみ)** を維持して hidden class の安定性を確保。\n", + "* 一時オブジェクトや配列の生成を避ける(`need`・`j` 程度のスカラのみ)。\n", + "* ソート案(O(n log n))は元添字保持のため追加配列が必要になり、今回は採用しない。\n", + "\n", + "この問題はすでに**定数因子**での勝負なので、伸びしろは小さいですが、**LeetCodeの実行環境(ジャッジ専用)での微調整**はまだできます。要点だけ絞って提案します。\n", + "\n", + "---\n", + "\n", + "## 改善ポイント(実務版 → ジャッジ最適化版)\n", + "\n", + "* **入力検証の削減**:LeetCodeは制約前提なので、重めの型/範囲チェックは外すと定数因子ぶん速く・軽くなります。\n", + "* **`Map` → `Object.create(null)`**:数値キーが文字列化されるデメリットはありますが、プロトタイプを持たないプレーン辞書は**メモリのオーバーヘッドが小さめ**になりやすいです(キー数が多いと顕著)。\n", + "\n", + " * `in` 判定も `Object.create(null)` なら安全(継承プロパティなし)。\n", + " * `undefined` を未登録の番人値として使えるため追加の `has`/`delete` が不要。\n", + "* **1パス & 1回参照**:`get→has` の二度引きを避け、**1回の読み出し結果で分岐**する現行方針は最適なので維持。\n", + "\n", + "---\n", + "\n", + "## ジャッジ最適化版(LeetCodeフォーマット/CommonJS・外部Libなし)\n", + "\n", + "```javascript\n", + "'use strict';\n", + "\n", + "/**\n", + " * @param {number[]} nums\n", + " * @param {number} target\n", + " * @return {[number, number]}\n", + " * @complexity 時間 O(n), 空間 O(n)\n", + " */\n", + "var twoSum = function(nums, target) {\n", + " // Object.create(null) で原型チェーンを断ち、軽量辞書を確保\n", + " const seen = Object.create(null); // value(string化) -> index\n", + "\n", + " // 1パス:未登録なら格納、補数があれば即返す\n", + " for (let i = 0, n = nums.length; i < n; i++) {\n", + " const x = nums[i];\n", + " const need = target - x;\n", + "\n", + " // 未登録は undefined(番人値)になる\n", + " const j = seen[need];\n", + " if (j !== undefined) return [j, i];\n", + "\n", + " // 最初の出現だけ保持(同値重複時に初出を優先)\n", + " if (seen[x] === undefined) seen[x] = i;\n", + " }\n", + "\n", + " // 問題の前提では到達しない\n", + " return [-1, -1];\n", + "};\n", + "\n", + "module.exports = twoSum;\n", + "\n", + "Analyze Complexity\n", + "Runtime 1 ms\n", + "Beats 87.91%\n", + "Memory 57.14 MB\n", + "Beats 35.94%\n", + "\n", + "```\n", + "\n", + "### 期待効果\n", + "\n", + "* **Runtime**:入力検証・`Map` の内部オーバーヘッド分が減り、1ms帯の安定性が上がる可能性\n", + "* **Memory**:`Map` よりもやや低く出ることが多い(キー数が1e4近いケースで差が出やすい)\n", + "\n", + "---\n", + "\n", + "## もう一段の微調整(好みで選択)\n", + "\n", + "* **`in` を使う版**(可読性重視)\n", + "\n", + " ```js\n", + " if (need in seen) return [seen[need], i];\n", + " if (!(x in seen)) seen[x] = i;\n", + " ```\n", + "\n", + " `Object.create(null)` 前提なので継承衝突なし。`in` は存在判定に明示的で、`undefined` 番人に依存しないスタイル。\n", + "\n", + "* **`Map` のまま行く派**\n", + " V8の`Map`は数値キー最適化が効きやすく、実測で速いケースもあるため、**環境依存**で逆転します。もし `Object` 版で悪化するなら元の `Map` 実装に戻すのが正解です(ベンチ前提)。\n", + "\n", + "---\n", + "\n", + "## まとめ\n", + "\n", + "* すでに **ほぼ上限近い性能**です。\n", + "* **ジャッジ専用なら**上記「Object辞書・検証削減」で**僅かなRuntime安定化とMemory微減**が狙えます。\n", + "* 逆に**業務コード**としては、これまでの**入力検証あり・Map版**のほうが堅牢です。用途に応じて使い分けがベストです。\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/DataStructures/Map/leetcode/gpt/Two_Sum_py.ipynb b/DataStructures/Map/leetcode/gpt/Two_Sum_py.ipynb new file mode 100644 index 00000000..1a454358 --- /dev/null +++ b/DataStructures/Map/leetcode/gpt/Two_Sum_py.ipynb @@ -0,0 +1,243 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7dafc4f3", + "metadata": {}, + "source": [ + "### 1. 問題分析結果\n", + "\n", + "#### 競技プログラミング視点\n", + "\n", + "* **最速手法**: 値→添字のハッシュ(`dict[int, int]`)を用いた**1パス**。各要素 `x` に対し補数 `target-x` が既出かを **平均 O(1)** で照会し、見つかったら即返す → **時間 O(n)**。\n", + "* **メモリ最小化**: 最初の出現のみ格納(同値が複数あっても最左のみ保持)。配列は非破壊・イミュータブルな読み取り。\n", + "\n", + "#### 業務開発視点\n", + "\n", + "* **型安全性/可読性**: `List[int]` 明示・戻り値も `List[int]` 固定で *pylance* 互換。責務が明確なローカル変数名(`seen`, `need`)。\n", + "* **エラーハンドリング**: LeetCode 前提では省略可だが、実務版では型・長さ検証を行い `TypeError/ValueError` を送出するメソッドも併設。\n", + "\n", + "#### Python特有分析\n", + "\n", + "* **CPython特性**: `dict` はハッシュテーブルのC実装で高速(平均 O(1))。`enumerate` により Python レベルのインデックス管理コストを削減。\n", + "* **GIL**: 単一スレッドCPUバウンドのため影響無し。I/Oも絡まない。\n", + "* **内蔵最適化**: ループ内のローカル変数束縛・属性/グローバル参照の回避でバイトコード最適化が乗りやすい。\n", + "\n", + "---\n", + "\n", + "### 2. アルゴリズム比較表\n", + "\n", + "| アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 |\n", + "| -------------------- | ---------- | ----- | ----------- | --- | ------------------- | ---------- | -------------------- |\n", + "| 方法A:ハッシュ1パス(`dict`) | **O(n)** | O(n) | 低 | 高 | `dict`, `enumerate` | **適** | 最速・最小定数因子 |\n", + "| 方法B:ソート+二ポインタ(元添字保持) | O(n log n) | O(n) | 中 | 中 | `sorted` | 適 | (値, 添字) ペア生成が必要 |\n", + "| 方法C:二重ループ | O(n²) | O(1) | 低 | 高 | なし | 不適 | 小規模のみ妥当、Follow-up不満足 |\n", + "\n", + "---\n", + "\n", + "### 3. 採用アルゴリズムと根拠\n", + "\n", + "* **選択**: 方法A(ハッシュ1パス)\n", + "* **理由**:\n", + "\n", + " * 計算量で最良(**O(n)**)。一意解保証により見つかった時点で即 return。\n", + " * CPython での `dict` はC実装で高速、かつコードは短く保守性も高い。\n", + " * 型注釈が簡潔で *pylance* と相性が良い(`List[int]` → `List[int]`)。\n", + "* **Python最適化戦略**:\n", + "\n", + " * `for i, x in enumerate(nums)` のローカル束縛で高速化。\n", + " * `dict.get(need)` の結果を一時変数に保持し二度引きを避ける。\n", + " * 既出位置は**最初の出現のみ**保持(重複値でバグらない)。\n", + "\n", + "---\n", + "\n", + "### 3. 実装パターン\n", + "\n", + "* **競技向け最適化版**(LeetCode本体が呼ぶ `twoSum`): 入力は正当と仮定し最短・最速で実装。\n", + "* **業務向け堅牢版**(補助メソッド): 型/長さ検証を行い、安全側に倒す。\n", + "\n", + "---\n", + "\n", + "### 4. 検証\n", + "\n", + "* **境界値**: `nums` 長さ最小の 2、同値ペア(例: `[3,3], 6`)、負数や大きな値の混在、同値が複数あるケース(最左保持で正常動作)。\n", + "* **型チェック**: `List[int]` のみ許容(*pylance* で不正引数を検出可能)。\n", + "\n", + "---\n", + "\n", + "## 実装(LeetCode / Class 形式, CPython 3.11, 型注釈あり)\n", + "\n", + "```python\n", + "from typing import List, Sequence\n", + "\n", + "\n", + "class Solution:\n", + " \"\"\"\n", + " Two Sum solver.\n", + "\n", + " - twoSum: 競技プログラミング向け最適化(LeetCode が呼び出す想定)\n", + " - two_sum_production: 業務向け(防御的検証あり)の参考実装\n", + " \"\"\"\n", + "\n", + " def twoSum(self, nums: List[int], target: int) -> List[int]:\n", + " \"\"\"\n", + " ハッシュ1パスで解く最速実装(LeetCode前提: 正当入力)\n", + "\n", + " Args:\n", + " nums: 整数配列\n", + " target: 目標和\n", + "\n", + " Returns:\n", + " 条件を満たす2インデックス(任意順)\n", + "\n", + " Time Complexity:\n", + " O(n), where n = len(nums)\n", + " Space Complexity:\n", + " O(n) in the worst case (ハッシュに最大 n-1 件保持)\n", + " \"\"\"\n", + " seen: dict[int, int] = {} # value -> first index\n", + " for i, x in enumerate(nums):\n", + " need = target - x\n", + " j = seen.get(need)\n", + " if j is not None:\n", + " return [j, i]\n", + " # 同じ要素を2度使わないため、先に照会→後で登録\n", + " if x not in seen:\n", + " seen[x] = i\n", + " # 問題の前提では到達しないが、型整合のため固定値を返す\n", + " return [-1, -1]\n", + "\n", + " # ---- 参考:業務向けの堅牢版(LeetCodeでは使用されない) ----\n", + " def two_sum_production(self, nums: Sequence[int], target: int) -> List[int]:\n", + " \"\"\"\n", + " 防御的検証ありの実務版(参考)。\n", + " Raises を積極的に使い、契約違反を明示。\n", + "\n", + " Raises:\n", + " TypeError: 非整数が混入している場合\n", + " ValueError: 長さが制約外の場合\n", + " \"\"\"\n", + " # 1) 型と値の検証\n", + " if not isinstance(nums, Sequence):\n", + " raise TypeError(\"nums must be a sequence of ints\")\n", + " n = len(nums)\n", + " if n < 2:\n", + " raise ValueError(\"nums must contain at least two elements\")\n", + " # LeetCode の制約に合わせるなら 2 <= n <= 10**4 程度でよいが任意\n", + " for idx, v in enumerate(nums):\n", + " if not isinstance(v, int):\n", + " raise TypeError(f\"nums[{idx}] must be int, got {type(v).__name__}\")\n", + " if not isinstance(target, int):\n", + " raise TypeError(\"target must be int\")\n", + "\n", + " # 2) メインアルゴリズム(twoSum と同一ロジック)\n", + " seen: dict[int, int] = {}\n", + " for i, x in enumerate(nums):\n", + " need = target - x\n", + " j = seen.get(need)\n", + " if j is not None:\n", + " return [j, i]\n", + " if x not in seen:\n", + " seen[x] = i\n", + "\n", + " # 本来到達しない前提だが、契約的に例外化するなら以下\n", + " raise ValueError(\"No valid two-sum solution found\")\n", + "\n", + "Analyze Complexity\n", + "Runtime 1 ms\n", + "Beats 57.34%\n", + "Memory 18.92 MB\n", + "Beats 33.85%\n", + "\n", + "```\n", + "\n", + "**要点まとめ**\n", + "\n", + "* `dict` 1パスで **O(n)** / **O(n)**、CPythonのC実装で高速。\n", + "* *pylance* フレンドリーな厳密型注釈(`List[int]` → `List[int]`)。\n", + "* LeetCode 用は最短実装、実務版はバリデーションを追加して安全性を担保。\n", + "\n", + "Two Sum はもう **定数因子勝負**なので伸びしろは小さめですが、LeetCode 実行環境向けの**マイクロ最適化**で数%の改善が出ることがあります。\n", + "\n", + "---\n", + "\n", + "## 伸ばせる可能性があるポイント\n", + "\n", + "1. **“補数を辞書に入れる” 方式**\n", + "\n", + "* ループで毎回 `need = target - x` を計算するのは同じですが、\n", + " 「`x` が既に辞書にあるか?」で判定でき、**減算→辞書アクセスの回数を1回**にできます。\n", + " (従来:`get(need)` → miss なら `set(x)`、提案:`if x in seen` → miss なら `seen[target-x]=i`)\n", + "\n", + "2. **メソッドのローカル束縛**\n", + "\n", + "* `seen.get` / `seen.__setitem__` / `seen.__contains__` をローカルに束縛すると**属性解決のオーバーヘッド**を削減できます(CPythonの定番小技)。\n", + "\n", + "3. **分岐の削減**\n", + "\n", + "* `in` 判定は `get` の `None` 判定よりわずかに有利なことがあります(キー 0 の扱いも明確)。\n", + "\n", + "4. **例外パスを使わない**\n", + "\n", + "* `try/except KeyError` は早いケースもありますが、ミスヒット頻度が高い本問題では `in`/`get` の方が安定しがち。\n", + "\n", + "> メモリについては辞書必須のため劇的改善は難しいですが、**初出のみ格納**(重複時に上書きしない)は継続が最良です。\n", + "\n", + "---\n", + "\n", + "## マイクロ最適化版(LeetCode / Class 形式, CPython 3.11, 型注釈あり)\n", + "\n", + "```python\n", + "from typing import List\n", + "\n", + "\n", + "class Solution:\n", + " def twoSum(self, nums: List[int], target: int) -> List[int]:\n", + " \"\"\"\n", + " Complement-first dict + method binding による軽量実装\n", + " Time: O(n), Space: O(n)\n", + " \"\"\"\n", + " seen: dict[int, int] = {} # key: complement(= target - value), val: index\n", + " contains = seen.__contains__ # ローカル束縛で属性解決を削減\n", + " setitem = seen.__setitem__ # 代入もバインドして高速化\n", + " # ループ: 先に「x が辞書にあるか?」を見て、無ければ complement を登録\n", + " for i, x in enumerate(nums):\n", + " if contains(x):\n", + " return [seen[x], i]\n", + " setitem(target - x, i)\n", + " return [-1, -1] # 問題前提では到達しない\n", + "\n", + "Analyze Complexity\n", + "Runtime 2 ms\n", + "Beats 52.41%\n", + "Memory 19.18 MB\n", + "Beats 10.37%\n", + "\n", + "```\n", + "\n", + "### 変更点の効果\n", + "\n", + "* **辞書アクセス1回化**:`x in seen` → 1回、ヒット時は即 return。\n", + "* **属性解決の削減**:`contains` / `setitem` をローカルで保持。\n", + "* **同値処理の簡潔化**:初出のみ保持は暗黙に満たされます(後続は上書きせず、常に「補数」を登録するため)。\n", + "\n", + "---\n", + "\n", + "## それでも伸びない場合\n", + "\n", + "* LeetCode の計測は**ノイズが大きい**ため、数回提出して中央値で判断してください。\n", + "* もしメモリが気になる場合は、**コメントや余分な一時変数を減らす**程度(影響はごく小)です。\n", + "\n", + "**まとめ**: 既に上位帯ですが、上記の**補数辞書 + メソッド束縛**は Python では定番の最終調整です。\n", + "\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/DataStructures/Map/leetcode/gpt/Two_Sum_ts.ipynb b/DataStructures/Map/leetcode/gpt/Two_Sum_ts.ipynb new file mode 100644 index 00000000..18590e3e --- /dev/null +++ b/DataStructures/Map/leetcode/gpt/Two_Sum_ts.ipynb @@ -0,0 +1,237 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2075e78f", + "metadata": {}, + "source": [ + "### 1. 問題の分析\n", + "\n", + "* **競技プログラミング視点での分析**\n", + "\n", + " * 最速は値→添字のハッシュ(`Map`)を使う**1パス法**。各要素 `x` に対し補数 `target - x` を先に見たかを **O(1)** 期待で照会し、見つかれば即返す → **時間 O(n)**。\n", + " * メモリは最悪で n 件の値を保持 → **空間 O(n)**。配列は不変操作(非破壊)で GC 負荷を極小化。\n", + "\n", + "* **業務開発視点での分析**\n", + "\n", + " * **型安全性**:引数は `number[]`。実装では不変利用(副作用なし)。\n", + " * **保守性/可読性**:`seen`(値→最初の添字)という責務限定の構造、早期 return、例外メッセージの明確化。\n", + " * **エラーハンドリング**:制約外入力(長さ/有限数でない)を検知して `TypeError` / `RangeError` を送出(LeetCode では常に正当入力だが、業務品質では有用)。\n", + "\n", + "* **TypeScript特有の考慮点**\n", + "\n", + " * **型推論**:ローカル変数は `const`/`let` で自動推論。戻り値は `number[]` を明示。\n", + " * **コンパイル時最適化**:ユニオン/ジェネリクスは不要(数値専用)なので最短経路で最適化を阻害しない。\n", + " * **null安全性**:`Map#get` の戻りを一時変数に取り、`undefined` 判定を単純分岐で処理。\n", + "\n", + "---\n", + "\n", + "### 2. アルゴリズムアプローチ比較\n", + "\n", + "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", + "| ----------------------- | ---------- | ----- | ------- | ---- | --- | --------------------- |\n", + "| 方法A:ハッシュ1パス(値→添字 `Map`) | **O(n)** | O(n) | 低 | 高 | 高 | 補数照会→即時 return(最適) |\n", + "| 方法B:ソート+二ポインタ(元添字保持) | O(n log n) | O(n) | 中 | 中 | 中 | 添字復元用の配列が必要 |\n", + "| 方法C:二重ループ | O(n²) | O(1) | 低 | 高 | 高 | 小規模のみ妥当、Follow-up 非充足 |\n", + "\n", + "---\n", + "\n", + "### 3. 選択したアプローチと理由\n", + "\n", + "* **選択したアプローチ**: 方法A(ハッシュ1パス)\n", + "* **理由**:\n", + "\n", + " * **計算量**:Follow-up 要件(O(n²) より良い)を満たす **O(n)**。\n", + " * **TypeScript**:`Map` が型安全で記述量も最小。\n", + " * **保守性/可読性**:単純な for ループ+早期 return。副作用なしの Pure 実装。\n", + "* **TypeScript特有の最適化ポイント**:\n", + "\n", + " * 数値専用のためジェネリクスを使わず、型を絞って JIT/最適化を阻害しない。\n", + " * `const` 利用で再代入防止、`readonly` 的運用(配列は不変操作)。\n", + "\n", + "---\n", + "\n", + "### 4. 実装コード(LeetCode 形式・ESM方針 / Node.js v22.14.0, 外部ライブラリ不可)\n", + "\n", + "> 注: LeetCode の TypeScript ランタイムは関数のみを評価します(`export` は不要/非推奨)。本実装は **Pure function** で、ローカル ESM 環境でもそのまま動作します(関数定義のみのためモジュール化は不要)。\n", + "\n", + "```typescript\n", + "/**\n", + " * Two Sum — 1パスのハッシュ法(Pure)\n", + " * @param nums 入力配列(整数のみを想定、非破壊で扱う)\n", + " * @param target 目標和\n", + " * @returns 条件を満たす 2 要素の添字(任意順)\n", + " * @throws {TypeError} nums が配列でない / 要素や target が有限数でない場合\n", + " * @throws {RangeError} 配列長が [2, 1e4] を外れる場合(業務品質のための防御的検証)\n", + " * @complexity Time: O(n), Space: O(n)\n", + " */\n", + "function twoSum(nums: number[], target: number): number[] {\n", + " // --- 入力検証(LeetCode では前提満たすが、業務品質として保持) ---\n", + " if (!Array.isArray(nums)) {\n", + " throw new TypeError('nums must be an array');\n", + " }\n", + " const n = nums.length;\n", + " if (n < 2 || n > 1e4) {\n", + " throw new RangeError('nums length is out of range [2, 1e4]');\n", + " }\n", + " if (!Number.isFinite(target)) {\n", + " throw new TypeError('target must be a finite number');\n", + " }\n", + " // 要素は有限数であることを軽量チェック\n", + " for (let i = 0; i < n; i++) {\n", + " const v = nums[i];\n", + " if (!Number.isFinite(v)) {\n", + " throw new TypeError(`nums[${i}] must be a finite number`);\n", + " }\n", + " }\n", + "\n", + " // --- 本処理(値→最初の添字) ---\n", + " const seen = new Map(); // value -> index\n", + "\n", + " for (let i = 0; i < n; i++) {\n", + " const x = nums[i];\n", + " const need = target - x;\n", + " const j = seen.get(need);\n", + " if (j !== undefined) {\n", + " // 一意解前提につき即 return(添字の順不同)\n", + " return [j, i];\n", + " }\n", + " // 同値重複時は最初の出現を固定\n", + " if (!seen.has(x)) seen.set(x, i);\n", + " }\n", + "\n", + " // 問題設定では必ず見つかるが、防御的に例外より固定値を返すなら下記:\n", + " // return [-1, -1];\n", + " throw new RangeError('No valid two-sum solution found');\n", + "}\n", + "\n", + "Analyze Complexity\n", + "Runtime 2 ms\n", + "Beats 74.43%\n", + "Memory 57.05 MB\n", + "Beats 48.39%\n", + "\n", + "```\n", + "\n", + "---\n", + "\n", + "## TypeScript固有の最適化観点\n", + "\n", + "* **型安全性の活用**\n", + "\n", + " * 問題が数値専用のため、敢えてジェネリクスを使わず `number[]` に限定し、呼び出し側の誤用をコンパイル時に排除。\n", + " * 実行時は `Number.isFinite` による軽量ガードでダブルセーフティ。\n", + "\n", + "* **コンパイル時最適化**\n", + "\n", + " * ループは `for (let i = 0; i < n; i++)` の単純形で分岐予測と最適化が安定。\n", + " * 余計なオブジェクト生成を避け、`Map#get` の戻りを一時変数に保持して分岐(再取得なし)。\n", + "\n", + "* **開発効率と保守性**\n", + "\n", + " * 副作用なしの **Pure function**、早期 `return` により読みやすくテスト容易。\n", + " * 例外はホットパス外(先頭検証)で発生し、計測パスを汚さない。\n", + "\n", + "**さらに数%の伸び**と**わずかなメモリ削減**を狙うなら、LeetCodeの前提(常に妥当入力)を活かして「防御的チェックを外す」「`Map`→nullプロトタイプの辞書」を試す価値があります。下記2点が要点です。\n", + "\n", + "* **Runtime 改善**: 先頭の型/範囲チェックを外す(LeetCodeは制約前提)。`Map#get/has` より軽い **`Object.create(null)` 辞書 + `in` 判定** に変更。\n", + "* **Memory 改善**: `Map` は内部構造のオーバーヘッドがあり、**キーが多いほど辞書(null-proto)有利**になりがち。\n", + "\n", + "---\n", + "\n", + "## 提案:ジャッジ最適化版(TypeScript / ESM方針、LeetCode関数フォーマット)\n", + "\n", + "```typescript\n", + "/**\n", + " * Two Sum — Judge-optimized (TS / Pure)\n", + " * @param nums 数値配列(LeetCode前提で妥当入力)\n", + " * @param target 目標和\n", + " * @returns 条件を満たす 2 要素の添字\n", + " * @complexity Time: O(n), Space: O(n)\n", + " */\n", + "function twoSum(nums: number[], target: number): number[] {\n", + " // nullプロトタイプ辞書:プロトタイプ汚染なし、キー存在判定が軽い\n", + " const seen: Record = Object.create(null);\n", + "\n", + " // 1パス:補数が既出なら即返す / 未登録なら初出を保持\n", + " // *同じ要素を二度使わない条件を満たすため「先に照会→後で登録」\n", + " for (let i = 0, n = nums.length; i < n; i++) {\n", + " const x = nums[i];\n", + " const need = target - x;\n", + " const kNeed = need as unknown as string; // 数値キーは内部的に文字列化\n", + "\n", + " if (kNeed in seen) {\n", + " return [seen[kNeed], i];\n", + " }\n", + "\n", + " const kX = x as unknown as string;\n", + " if (!(kX in seen)) {\n", + " seen[kX] = i; // 最初の出現のみ保持(重複値で最左を固定)\n", + " }\n", + " }\n", + "\n", + " // 問題の前提では解が必ず存在\n", + " return [-1, -1];\n", + "}\n", + "\n", + "Analyze Complexity\n", + "Runtime 3 ms\n", + "Beats 64.32%\n", + "Memory 56.44 MB\n", + "Beats 71.03%\n", + "\n", + "```\n", + "\n", + "### 変更点と狙い\n", + "\n", + "* **入力検証を削除**:純粋にホットパスのみ。LeetCode前提で安全。\n", + "* **`Map`→`Record(null-proto)`**:キー変換は発生しますが、要素数が増えるほど軽量辞書が効くケースが多いです。\n", + "* **`in` 判定**:`undefined` 番人値に依存せず、存在チェックを明示・高速化。\n", + "\n", + "---\n", + "\n", + "## 代替:保守性重視(業務コード寄り)\n", + "\n", + "入力検証あり・`Map` のまま運用(読みやすさ・意図の明確さ重視)。LeetCodeの計測では若干不利になり得ますが、現場品質としては推奨です。\n", + "\n", + "```typescript\n", + "function twoSum(nums: number[], target: number): number[] {\n", + " const n = nums.length;\n", + " const seen = new Map();\n", + "\n", + " for (let i = 0; i < n; i++) {\n", + " const x = nums[i];\n", + " const j = seen.get(target - x);\n", + " if (j !== undefined) return [j, i];\n", + " if (!seen.has(x)) seen.set(x, i);\n", + " }\n", + " return [-1, -1];\n", + "}\n", + "\n", + "Analyze Complexity\n", + "Runtime 4 ms\n", + "Beats 55.73%\n", + "Memory 57.38 MB\n", + "Beats 38.39%\n", + "\n", + "```\n", + "\n", + "---\n", + "\n", + "## まとめ\n", + "\n", + "* **ジャッジ最適化版**:防御チェック排除 + null-proto辞書 + `in` → わずかに速く・やや省メモリになりやすい。\n", + "* **業務版**:`Map` + 最小限の検証で可読・保守優先。\n", + " どちらも **Time O(n), Space O(n)**・Pure function・ESM/TS で問題要件を満たします。\n", + "\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From e11975c8df08ecc6baddbce10db687cb4a5ab749 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Wed, 5 Nov 2025 15:22:32 +0900 Subject: [PATCH 2/2] Fix: 67. Add Binary/Claude/README.html file #198 --- .../67. Add Binary/Claude/README.html | 1664 ----------------- .../leetcode/67. Add Binary/Claude/README.md | 54 +- .../67. Add Binary/Claude/README_react.html | 1505 +++++++++++++++ .../Map/leetcode/claude/README_react.html | 2 +- .../Map/leetcode/gpt/Two_Sum_js.ipynb | 474 ++--- 5 files changed, 1783 insertions(+), 1916 deletions(-) delete mode 100644 Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.html create mode 100644 Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README_react.html diff --git a/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.html b/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.html deleted file mode 100644 index e79d0b50..00000000 --- a/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.html +++ /dev/null @@ -1,1664 +0,0 @@ - - - - - - LeetCode 67: Add Binary - Two-Pointer + Carry方式 - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

- アルゴリズム概要 -

- -

問題

-

- 2つの2進数文字列 a と - b - が与えられたとき、それらの和を2進数文字列として返す。 -

- -

入出力例

-
-

例1:

-

入力: a = "11", b = "1"

-

出力: "100"

-
-

例2:

-

入力: a = "1010", b = "1011"

-

出力: "10101"

-
- -

制約

-
    -
  • 1 ≤ a.length, b.length ≤ 10⁴
  • -
  • a と b は '0' と '1' のみで構成される
  • -
  • 先頭ゼロは存在しない("0" 自体を除く)
  • -
- -

戦略

-
    -
  • Two-Pointer: 末尾(最下位桁)から順に走査
  • -
  • Carry(桁上がり): 各桁で 0 または 1 のキャリーを管理
  • -
  • - 固定長バッファ: - bytearray - に直接ASCII文字を書き込み -
  • -
  • - bytes化: - encode('ascii') で各反復の - ord() 呼び出しを排除 -
  • -
- -

主要ポイント

-
    -
  • 時間計算量: O(n) ※ n = max(len(a), len(b))
  • -
  • 空間計算量: O(1) 補助空間(出力バッファを除く)
  • -
  • - 最適化手法: ビット演算(& 1)で分岐レスにビット取得 -
  • -
-
- - -
-

- ステップバイステップ解説 -

-
-
- - -
-

- Python実装 -

-
from __future__ import annotations
-from typing import Final
-
-
-class Solution:
-    """
-    Add Binary
-    2つの2進数文字列の和を2進数文字列として返す
-    """
-
-    def addBinary(self, a: str, b: str) -> str:
-        """
-        2つの2進数文字列 a, b の和を2進数文字列で返す
-
-        Args:
-            a: 2進文字列('0'/'1' のみ、長さ1〜10^4)
-            b: 同上
-
-        Returns:
-            a + b の2進表現
-
-        Complexity:
-            Time: O(n), Space: O(1) auxiliary
-        """
-        # 早期リターン: 一方が "0" の場合
-        if a == "0":
-            return b
-        if b == "0":
-            return a
-
-        # bytes化で各反復の ord() 呼び出しを排除
-        a_bytes: bytes = a.encode("ascii")
-        b_bytes: bytes = b.encode("ascii")
-
-        # インデックス初期化(末尾から開始)
-        i: int = len(a_bytes) - 1
-        j: int = len(b_bytes) - 1
-        k: int = (i if i > j else j) + 1  # 書き込み位置(キャリー分+1)
-        carry: int = 0
-
-        ZERO: Final[int] = 48  # ASCII '0'
-        out = bytearray(k + 1)  # 最上位桁のキャリー用に+1
-
-        # 末尾から1パスで加算
-        while i >= 0 or j >= 0 or carry:
-            s = carry
-
-            # a の現在の桁を加算(範囲内の場合)
-            if i >= 0:
-                s += (a_bytes[i] & 1)  # '0'(48) -> 0, '1'(49) -> 1
-                i -= 1
-
-            # b の現在の桁を加算(範囲内の場合)
-            if j >= 0:
-                s += (b_bytes[j] & 1)
-                j -= 1
-
-            # 結果の桁を書き込み(s の最下位ビット)
-            out[k] = ZERO + (s & 1)
-
-            # キャリーを更新(s を右シフト)
-            carry = s >> 1
-            k -= 1
-
-        # k+1 以降が有効な結果領域
-        start: int = k + 1
-        return out[start:].decode("ascii")
-
- - -
-

- フローチャート -

-
- - - - - - - - - - - - - - - - - - - - 開始 - - - - - - - a または b が - "0" か? - - - - - - はい - - - - 他方を - 返す - - - - - - - いいえ - - - - a, b を - bytes に変換 - - - - - - - i = len(a) - 1 - j = len(b) - 1 - carry = 0, out = bytearray() - - - - - - - i ≥ 0 または - j ≥ 0 または carry? - - - - - - いいえ - - - - - - はい - - - - s = carry - - - - - - - i ≥ 0 ? - - - - - はい - - - - s += a[i] & 1 - i -= 1 - - - - - - - いいえ - - - - j ≥ 0 ? - - - - - いいえ - - - - out[k] = '0'+'1' - carry = s >> 1 - k -= 1 - - - - - - はい - - - - s += b[j] & 1 - j -= 1 - - - - - - out[start:] - .decode("ascii") - - - - - - - 終了 - - -
- -

- フローの説明:
- 1. 早期チェックで一方が "0" の場合は他方を返す
- 2. 入力を bytes に変換して ord() 呼び出しを排除
- 3. インデックス i, j, k と carry を初期化
- 4. ループ条件:i ≥ 0 または j ≥ 0 または carry がある
- 5. 各桁で s = carry + a[i] + b[j] を計算
- 6. 結果の桁を out[k] に書き込み、carry を更新
- 7. すべての桁を処理したら decode して返す -

-
- - -
-

- 計算量分析 -

- -

時間計算量

-

- O(n) ※ n = max(len(a), len(b)) -

-
    -
  • - encode('ascii'): O(len(a)) + - O(len(b)) -
  • -
  • メインループ: 最大 n+1 回の反復(キャリー分)
  • -
  • 各反復での操作: ビット取得・加算・書き込みはすべて O(1)
  • -
  • decode('ascii'): O(n)
  • -
- -

空間計算量

-

- O(1) 補助空間(出力バッファを除く) -

-
    -
  • - a_bytes, - b_bytes: - 入力サイズに比例(O(n))だが入力として必要 -
  • -
  • - out (bytearray): - 出力として必要な O(n) -
  • -
  • その他の変数(i, j, k, carry): O(1)
  • -
- -

アプローチ比較

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
アプローチ時間計算量空間計算量特徴
- Two-Pointer + bytearray(採用) - O(n)O(1)最速、メモリ効率最高
- list.append + reverse + join - O(n)O(n) - 実装簡単だが中間配列必要 -
int変換 + bin()O(n)O(n) - 大きい数で非効率、多倍長演算 -
-
-
- - - - - - - - - - - - diff --git a/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.md b/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.md index e07f80f3..1c22dc57 100644 --- a/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.md +++ b/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.md @@ -19,12 +19,14 @@ **問題**: 2つの2進数文字列 `a` と `b` が与えられたとき、それらの和を2進数文字列として返す。 **要件**: + - 入力は `'0'` と `'1'` のみで構成される - 長さは 1 ≤ len(a), len(b) ≤ 10⁴ - 先頭ゼロは存在しない(`"0"` 自体を除く) - 出力も2進数文字列として正しい形式(先頭ゼロなし) **制約**: + - 文字列長が最大1万桁なので、整数変換(`int(a, 2)`)は可能だが非効率 - メモリ効率と実行速度の両立が求められる @@ -35,14 +37,17 @@ **戦略**: Two-Pointer + Carry(桁上がり)を用いた末尾からの1パス走査 **データ構造**: + - 入力を `bytes` に変換(`encode('ascii')`)してビット取得を高速化 - 出力用に固定長 `bytearray` を確保し、末尾から直接ASCII文字を書き込み **計算量**: + - **時間**: O(max(len(a), len(b))) = O(n) - **空間**: O(1) 補助空間(出力バッファを除く) **メモリ最適化**: + - `reverse()` や `insert(0, ...)` などの高コスト操作を回避 - 文字列の逐次結合(`+=`)を避け、最後に一度だけ `decode('ascii')` @@ -50,7 +55,7 @@

図解

-### フローチャート +## フローチャート ```mermaid flowchart TD @@ -70,6 +75,7 @@ flowchart TD ``` **説明**: + - 早期チェックで一方が `"0"` の場合は他方をそのまま返す - 文字列を `bytes` に変換して各反復での `ord()` 呼び出しを排除 - 末尾(最下位桁)から順に加算とキャリーを処理 @@ -97,6 +103,7 @@ graph LR ``` **説明**: + - 入力文字列を `bytes` に変換してビット取得を高速化 - Two-Pointerで末尾から走査し、各桁の和とキャリーを計算 - `bytearray` への直接書き込みでメモリ効率を最大化 @@ -107,18 +114,22 @@ graph LR

正しさのスケッチ

**不変条件**: + - 各反復で、位置 `i`, `j`, `k` に対して `k` 位置に正しい桁の結果が書き込まれる - `carry` は常に 0 または 1 の値を持つ(2進数の桁上がりは最大1) **網羅性**: + - `while i >= 0 or j >= 0 or carry` により、すべての桁とキャリーを処理 - 短い方の文字列が終わっても、長い方とキャリーの処理を続行 **基底条件**: + - `i < 0` かつ `j < 0` かつ `carry == 0` で終了 - 最上位桁のキャリーも `bytearray` に書き込まれる **終了性**: + - 各反復で `i`, `j`, `k` が減少 - 有限長の入力に対して必ず終了 @@ -127,6 +138,7 @@ graph LR

計算量

### 時間計算量 + **O(n)** ここで n = max(len(a), len(b)) - `encode('ascii')`: O(len(a)) + O(len(b)) @@ -135,6 +147,7 @@ graph LR - `decode('ascii')`: O(n) ### 空間計算量 + **O(1) 補助空間**(出力バッファを除く) - `a_bytes`, `b_bytes`: 入力サイズに比例(O(n))だが、入力として必要 @@ -143,11 +156,11 @@ graph LR ### アプローチ比較 -| アプローチ | 時間 | 空間 | 特徴 | -|---------|-----|-----|-----| -| **Two-Pointer + bytearray(採用)** | O(n) | O(1) | 最速、メモリ効率最高 | -| list.append + reverse + join | O(n) | O(n) | 実装簡単だが中間配列必要 | -| int変換 + bin() | O(n) | O(n) | 大きい数で非効率、多倍長演算 | +| アプローチ | 時間 | 空間 | 特徴 | +| ----------------------------------- | ---- | ---- | ---------------------------- | +| **Two-Pointer + bytearray(採用)** | O(n) | O(1) | 最速、メモリ効率最高 | +| list.append + reverse + join | O(n) | O(n) | 実装簡単だが中間配列必要 | +| int変換 + bin() | O(n) | O(n) | 大きい数で非効率、多倍長演算 | --- @@ -224,6 +237,7 @@ class Solution: ``` **実装のポイント**: + - `encode('ascii')` で文字列を `bytes` に変換し、各反復での `ord()` を排除 - `& 1` でビット取得('0'=48→0, '1'=49→1) - `bytearray` への直接書き込みで中間文字列生成を回避 @@ -234,6 +248,7 @@ class Solution:

CPython最適化ポイント

### 1. `bytes` 変換による `ord()` 排除 + ```python # 遅い: 各反復で ord() を呼び出し bit = ord(a[i]) - ord('0') @@ -244,6 +259,7 @@ bit = a_bytes[i] & 1 ``` ### 2. 固定長 `bytearray` への直接書き込み + ```python # 遅い: リスト append + reverse + join result = [] @@ -258,6 +274,7 @@ return out[start:].decode('ascii') ``` ### 3. 分岐の最小化 + ```python # 条件式でビット取得を統一 ai = (a_bytes[i] & 1) if i >= 0 else 0 @@ -265,12 +282,14 @@ bj = (b_bytes[j] & 1) if j >= 0 else 0 ``` ### 4. 定数のローカル化 + ```python ZERO: Final[int] = 48 # グローバル参照を避ける out[k] = ZERO + (s & 1) ``` ### パフォーマンス結果 + - **Runtime**: 0ms(100.00%) - **Memory**: 17.92MB(28.60%) @@ -281,21 +300,24 @@ out[k] = ZERO + (s & 1)

エッジケースと検証観点

### 境界値 -| ケース | 入力 | 期待出力 | 検証ポイント | -|-------|-----|---------|------------| -| 両方最小 | `a="0"`, `b="0"` | `"0"` | 早期リターン | -| 片方最小 | `a="0"`, `b="1"` | `"1"` | 早期リターン | -| 最小桁 | `a="1"`, `b="1"` | `"10"` | キャリー発生 | -| キャリー連鎖 | `a="1111"`, `b="1"` | `"10000"` | 全桁キャリー | -| 長さ差大 | `a="1"`, `b="1010101"` | `"1010110"` | 短い方の処理 | -| 最大長 | len(a)=10⁴, len(b)=10⁴ | 正しい和 | パフォーマンス | + +| ケース | 入力 | 期待出力 | 検証ポイント | +| ------------ | ---------------------- | ----------- | -------------- | +| 両方最小 | `a="0"`, `b="0"` | `"0"` | 早期リターン | +| 片方最小 | `a="0"`, `b="1"` | `"1"` | 早期リターン | +| 最小桁 | `a="1"`, `b="1"` | `"10"` | キャリー発生 | +| キャリー連鎖 | `a="1111"`, `b="1"` | `"10000"` | 全桁キャリー | +| 長さ差大 | `a="1"`, `b="1010101"` | `"1010110"` | 短い方の処理 | +| 最大長 | len(a)=10⁴, len(b)=10⁴ | 正しい和 | パフォーマンス | ### 特殊ケース + - **キャリーのみ残る**: `a="1"`, `b="1"` → `"10"` - **一方が極端に短い**: `a="1"`, `b="1"*10000` - **交互のビットパターン**: `a="1010"`, `b="0101"` → `"1111"` ### 型安全性 + - 入力は必ず `str` 型(LeetCodeの制約保証) - 内部は `bytes` と `int` のみで処理 - pylance で型エラーなし @@ -307,6 +329,7 @@ out[k] = ZERO + (s & 1) ### Q1: なぜ `int(a, 2) + int(b, 2)` を使わないのか? **A**: 文字列長が最大10⁴桁の場合、整数変換は以下の問題がある: + - 多倍長演算のオーバーヘッド - メモリ使用量の増加(大きな整数オブジェクト) - `bin()` での文字列化コスト @@ -320,6 +343,7 @@ Two-Pointer方式は O(n) の線形時間で、メモリも最小限。 ### Q3: なぜ `bytearray` を使うのか? **A**: + - ミュータブルなバイト列で、固定長バッファとして使用可能 - ASCII文字(数値)の直接書き込みが可能 - `reverse()` や文字列結合のコストを回避 @@ -328,6 +352,7 @@ Two-Pointer方式は O(n) の線形時間で、メモリも最小限。 ### Q4: 入力検証は必要か? **A**: LeetCodeの制約は保証されているため、本実装では省略。業務コードでは以下を検証: + - 型チェック(`isinstance(a, str)`) - 長さチェック(1 ≤ len ≤ 10⁴) - 文字チェック('0'/'1'のみ) @@ -340,6 +365,7 @@ Two-Pointer方式は O(n) の線形時間で、メモリも最小限。 ### Q6: より速くする方法は? **A**: + - 入力検証を完全に削除(LeetCodeでは不要) - ローカル変数への関数バインド(`append = list.append`) - ただし、現在の実装で既に 0ms(100%)を達成しており、これ以上の改善は環境依存 diff --git a/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README_react.html b/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README_react.html new file mode 100644 index 00000000..8f1b80a6 --- /dev/null +++ b/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README_react.html @@ -0,0 +1,1505 @@ + + + + + + LeetCode 67: Add Binary - 二進数加算 + + + + + + + + + + + + +
+ + +
+

+ アルゴリズム概要 +

+ +

問題の説明

+

+ 2つの二進数文字列 + a と + b + が与えられます。これらを加算し、結果を二進数文字列として返します。 +

+ +

入出力例

+
+

例1:

+
入力: a = "11", b = "1"
+出力: "100"
+説明: 11 (3) + 1 (1) = 100 (4)
+
+ +
+

例2:

+
入力: a = "1010", b = "1011"
+出力: "10101"
+説明: 1010 (10) + 1011 (11) = 10101 (21)
+
+ +

制約条件

+
    +
  • + 1 <= a.length, b.length <= 10⁴ +
  • +
  • + a と + b は + '0' または + '1' + のみで構成される +
  • +
  • 各文字列は先頭のゼロを含まない(ゼロ自体を除く)
  • +
+ +

戦略

+
    +
  • + 右から左へのビット加算: + 最下位ビット(右端)から順に加算を行う +
  • +
  • + キャリー処理: + 各桁の加算結果が2以上の場合、次の桁にキャリーを持ち越す +
  • +
  • 文字列の長さの違い: 短い文字列は0として扱う
  • +
  • + 結果の構築: 各桁の計算結果を文字列として構築し、最後に反転 +
  • +
+ +

主要ポイント

+
+

時間計算量: O(max(m, n))

+

+ m と n はそれぞれ文字列 a と b の長さ。各桁を1回ずつ処理します。 +

+

空間計算量: O(max(m, n))

+

+ 結果の文字列は最大で max(m, n) + 1 の長さになります。 +

+
+
+ +
+

+ ステップバイステップ解説 +

+
+
+ +
+

+ Python実装 +

+
class Solution:
+    def addBinary(self, a: str, b: str) -> str:
+        result = []
+        carry = 0
+        i, j = len(a) - 1, len(b) - 1
+
+        # 右から左へ各桁を処理
+        while i >= 0 or j >= 0 or carry:
+            # 現在の桁の値を取得(範囲外は0)
+            digit_a = int(a[i]) if i >= 0 else 0
+            digit_b = int(b[j]) if j >= 0 else 0
+
+            # 現在の桁の合計 = digit_a + digit_b + carry
+            total = digit_a + digit_b + carry
+
+            # 結果の桁を追加(total % 2)
+            result.append(str(total % 2))
+
+            # 次の桁へのキャリーを計算(total // 2)
+            carry = total // 2
+
+            # インデックスを左に移動
+            i -= 1
+            j -= 1
+
+        # 結果を反転して返す(右から左に構築したため)
+        return ''.join(reversed(result))
+
+ +
+

+ フローチャート +

+
+ + + + + + + + + + + + + + + + + + + + 開始 + + + + + + + 初期化 + result = [], carry = 0 + i = len(a)-1, j = len(b)-1 + + + + + + + i >= 0 または j >= 0 + または carry > 0? + + + + + + いいえ + + + + + + はい + + + + + 桁の値を取得 + digit_a = a[i] if i >= 0 else 0 + digit_b = b[j] if j >= 0 else 0 + + + + + + + 合計計算 + total = digit_a + digit_b + carry + + + + + + + 結果に追加 + result.append(str(total % 2)) + + + + + + + キャリー更新 + carry = total // 2 + + + + + + + インデックス更新 + i -= 1, j -= 1 + + + + + + 次の桁へ + + + + + + 結果を反転 + return ''.join(reversed(result)) + + + + + + + 終了 + + +
+ +

+ フローの説明:
+ 1. 初期化: + 結果配列、キャリー、インデックス(i, j)を初期化
+ 2. ループ条件: i または j + が0以上、またはキャリーがある間繰り返す
+ 3. 桁の値取得: + 各文字列から現在の桁の値を取得(範囲外は0)
+ 4. 合計計算: digit_a + digit_b + carry + を計算
+ 5. 結果追加: total % 2 + を結果配列に追加
+ 6. キャリー更新: total // 2 + を次のキャリーとして保存
+ 7. インデックス更新: i と j + をデクリメント
+ 8. ループバック: 次の桁へ戻る
+ 9. 結果反転: + 右から左に構築したため、結果を反転して返す +

+
+ +
+

+ 計算量分析 +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 指標 + + 本実装(ビット加算) + + 代替手法(整数変換) +
+ 時間計算量 + O(max(m, n))O(m + n)
+ 空間計算量 + O(max(m, n))O(max(m, n))
+ 実装の複雑さ + + 中程度(キャリー処理が必要) + + 簡単(組み込み関数使用) +
+ 大きな数への対応 + + ◎ 任意の長さ対応 + + △ 整数オーバーフローの可能性 +
+ 推奨度 + + ★★★★★ 最適 + + ★★☆☆☆ 小さい数のみ +
+
+ +
+

💡 最適化のポイント

+
    +
  • 各桁を1回ずつ処理するため、時間計算量は O(max(m, n)) で最適
  • +
  • キャリーの処理により、任意の長さの二進数文字列に対応可能
  • +
  • 整数変換による手法と異なり、オーバーフローの心配がない
  • +
  • + Pythonの + reversed() + は効率的にイテレータを返す +
  • +
+
+ +
+

⚠️ 注意点

+
    +
  • + 文字列のインデックスは右から左(最下位ビットから最上位ビット)に処理 +
  • +
  • キャリーは必ず0または1(二進数の性質)
  • +
  • ループ終了条件には carry も含める(最後のキャリーを忘れないため)
  • +
  • 結果は逆順で構築されるため、最後に反転が必要
  • +
+
+
+
+ + + + + + + + + + diff --git a/DataStructures/Map/leetcode/claude/README_react.html b/DataStructures/Map/leetcode/claude/README_react.html index d14c3a90..2ff98fba 100644 --- a/DataStructures/Map/leetcode/claude/README_react.html +++ b/DataStructures/Map/leetcode/claude/README_react.html @@ -471,7 +471,7 @@

Two Sum

を返却