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)
+
+ +

制約条件

+ + +

戦略

+ + +

主要ポイント

+
+

時間計算量: 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.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/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.html b/DataStructures/Map/leetcode/claude/README_react.html similarity index 52% rename from Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.html rename to DataStructures/Map/leetcode/claude/README_react.html index e79d0b50..2ff98fba 100644 --- a/Algorithm/TwoPointers/leetcode/67. Add Binary/Claude/README.html +++ b/DataStructures/Map/leetcode/claude/README_react.html @@ -3,7 +3,7 @@ - LeetCode 67: Add Binary - Two-Pointer + Carry方式 + Two Sum - ハッシュテーブル1パス探索 @@ -16,7 +16,7 @@ rel="stylesheet" /> - + - - - - -