From deeaaabc08d6199056518b78d5f32b1c7033dd86 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sat, 14 Mar 2026 20:16:22 +0900 Subject: [PATCH 1/4] Add LeetCode 88. Merge Sorted Array (Claude 4.6 Sonnet) implementations in Rust, Python, and TypeScript --- .../Merge_Sorted_Array_Python.md | 129 ++ .../Merge_Sorted_Array_Rust.md | 98 ++ .../Merge_Sorted_Array_TypeScript.md | 102 ++ .../88. Merge Sorted Array/README.md | 338 ++++ .../88. Merge Sorted Array/README_React.html | 1433 +++++++++++++++++ .../88. Merge Sorted Array/README_React.html | 1433 +++++++++++++++++ public/index.html | 10 +- 7 files changed, 3539 insertions(+), 4 deletions(-) create mode 100644 Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Python.md create mode 100644 Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Rust.md create mode 100644 Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_TypeScript.md create mode 100644 Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README.md create mode 100644 Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html create mode 100644 public/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html diff --git a/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Python.md b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Python.md new file mode 100644 index 00000000..404b7b40 --- /dev/null +++ b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Python.md @@ -0,0 +1,129 @@ +## 1. 問題分析結果 + +### 競技プログラミング視点 + +`nums1` の末尾から逆順に埋める3ポインタ法が最適。CPython では `list` のインデックスアクセスは O(1) の C 実装であり、追加リスト生成ゼロ・ヒープアロケーションゼロで O(m+n) を達成できます。 + +### 業務開発視点 + +LeetCode のシグネチャは `None` 返却の破壊的操作。Pylance 対応のため `List[int]` 型ヒントを厳密に付与し、`m`/`n` は `int` として明示します。エッジケース(`n == 0`)は早期リターンで明示的に処理します。 + +### Python特有分析 + +- `list.__setitem__` は C レイヤーで動作し、純粋な Python ループでも高速 +- `nums1[k], i, j, k` のローカル変数参照はグローバル参照より高速(LOAD_FAST) +- 競技版では `while` + インデックス直接操作が最速(イテレータ生成コスト不要) +- `nums1[:j+1] = nums2[:j+1]` のスライス代入は C の `memmove` 相当で残余コピーを一括処理可能 + +--- + +## 2. アルゴリズムアプローチ比較 + +| アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 | +| ---------------------- | ---------------- | ---------- | ---------------- | ------ | ------------------ | ------------------- | ------------------------- | +| 後ろから3ポインタ | O(m+n) | O(1) | 低 | ★★★ | なし | 適 | 追加アロケーションなし ✅ | +| コピー後 `list.sort()` | O((m+n)log(m+n)) | O(1) | 最低 | ★★★ | なし | 適(Timsort C実装) | 競技次善策 | +| `heapq.merge` + 展開 | O(m+n) | O(m+n) | 低 | ★★☆ | heapq | 適 | 一時リスト生成が発生 | + +--- + +## 3. 採用アルゴリズムと根拠 + +- **選択**: 後ろからの3ポインタマージ(業務版・競技版ともに同一戦略) +- **Python最適化戦略**: 残余コピーをスライス代入 `nums1[:j+1] = nums2[:j+1]` に置換することで、CPython 内部の `memmove` が発動し逐次代入より高速 +- **トレードオフ**: スライス代入は一時オブジェクトを1個生成するが定数サイズであり実質 O(1) の追加コスト + +--- + +## 4. 実装パターン + +```python +# Runtime 0 ms +# Beats 100.00% +# Memory 19.42 MB +# Beats 35.99% +from typing import List + +class Solution: + def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: + """ + 2つのソート済み配列を nums1 にインプレースでマージする。 + 後ろから走査する3ポインタ法により O(m+n) 時間・O(1) 追加空間を実現。 + + 不変条件: k >= i が常に成立するため nums1 の未処理要素を上書きしない。 + 初期値 k - i = n >= 0 が各イテレーションで保持される。 + + Args: + nums1: マージ先リスト(長さ m+n、末尾 n 要素は 0 で埋められている) + m: nums1 の有効要素数 + nums2: マージ元リスト(長さ n) + n: nums2 の要素数 + + Time Complexity: O(m + n) + Space Complexity: O(1) ※スライス代入の一時オブジェクト除く + """ + # ── 業務開発版 ───────────────────────────────────────────── + # n == 0 のとき nums1 は既に完成しているため早期リターン + if n == 0: + return + + i: int = m - 1 # nums1 有効末尾ポインタ + j: int = n - 1 # nums2 末尾ポインタ + k: int = m + n - 1 # 書き込み位置ポインタ + + # どちらかが尽きるまで大きい方を末尾に書き込む + while i >= 0 and j >= 0: + if nums1[i] >= nums2[j]: + nums1[k] = nums1[i] + i -= 1 + else: + nums1[k] = nums2[j] + j -= 1 + k -= 1 + + # nums2 の残余要素を一括スライス代入(CPython の memmove 相当で高速) + # nums1 の残余は既に正しい位置にあるため操作不要 + if j >= 0: + nums1[: j + 1] = nums2[: j + 1] + + def merge_competitive( + self, nums1: List[int], m: int, nums2: List[int], n: int + ) -> None: + """ + 競技プログラミング向け最適化実装。 + ローカル変数へのキャッシュで LOAD_FAST を最大活用。 + + Time Complexity: O(m + n) + Space Complexity: O(1) + """ + # ローカル変数へのバインドで属性ルックアップを排除 + i, j, k = m - 1, n - 1, m + n - 1 + + while i >= 0 and j >= 0: + if nums1[i] >= nums2[j]: + nums1[k] = nums1[i] + i -= 1 + else: + nums1[k] = nums2[j] + j -= 1 + k -= 1 + + if j >= 0: + nums1[: j + 1] = nums2[: j + 1] +``` + +--- + +## Python特有の追加考慮事項 + +**スライス代入 `nums1[:j+1] = nums2[:j+1]` の内部動作** + +CPython の `list_ass_slice` は `memmove` で連続メモリを一括コピーします。逐次 `while` ループに比べてインタープリタのディスパッチコストを排除でき、残余要素が多いほど効果的です。 + +**`LOAD_FAST` 最適化(競技版)** + +Python のバイトコードにおいて、メソッド内のローカル変数アクセス (`LOAD_FAST`) はグローバル変数アクセス (`LOAD_GLOBAL`) より約2倍高速です。`nums1`, `nums2` は引数として自動的にローカルスコープに入るため、競技版では追加のキャッシュは不要。カウンタ変数 `i`, `j`, `k` も同様です。 + +**Pylance 型安全性** + +`List[int]` を `from typing import List` で明示し、戻り値を `None` と宣言することで Pylance の strict モードでも警告ゼロを維持します。Python 3.9 以降では `list[int]` の組み込み記法も使用可能ですが、LeetCode の CPython 3.11 環境では `from typing import List` が最も互換性の高い記述です。 diff --git a/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Rust.md b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Rust.md new file mode 100644 index 00000000..91bb171f --- /dev/null +++ b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_Rust.md @@ -0,0 +1,98 @@ +## 1. 問題の分析 + +**競技プログラミング視点での分析** + +`nums1`の末尾から逆順に埋めていく「後ろからマージ」戦略が最適です。追加アロケーションなしでインプレース処理でき、時間 O(m+n)・空間 O(1) が実現できます。前から埋めると上書きが発生するため、後ろからスキャンするのが核心です。 + +**業務開発視点での分析** + +LeetCode の関数シグネチャは `&mut Vec` の受け渡しを強制しますが、内部では `usize` インデックスと `saturating_sub` を活用し、符号なし整数のアンダーフローをコンパイル時に潰します。`i32 → usize` の変換は `as usize` で確実に行います。 + +**Rust特有の考慮点** + +- `i32` 引数 `m`, `n` は LeetCode の制約上 `0..=200` に収まるため、`as usize` キャストは安全 +- `checked_sub` や `saturating_sub` でインデックス計算のパニックを防止 +- 借用は `&mut Vec` のスライス操作に集約し、ライフタイムを単純化 + +--- + +## 2. アルゴリズムアプローチ比較 + +| アプローチ | 時間計算量 | 空間計算量 | Rust実装コスト | 安全性 | 可読性 | 備考 | +| --------------------------- | ---------------- | ---------- | -------------- | ------ | ------ | ------------------------- | +| 後ろからマージ(3ポインタ) | O(m+n) | O(1) | 低 | 高 | 高 | 追加アロケーションなし ✅ | +| 前からコピー後ソート | O((m+n)log(m+n)) | O(1) | 低 | 高 | 高 | 標準の `.sort()` 利用可 | +| 一時バッファにコピー | O(m+n) | O(m) | 低 | 高 | 中 | Vec への clone が発生 | + +--- + +## 3. 選択したアルゴリズムと理由 + +- **選択**: 後ろからの 3 ポインタマージ +- **理由**: O(m+n) の時間・O(1) の追加空間。末尾から書き込むことで`nums1`の有効領域を上書きせずマージできる。Rustの所有権モデルと完全に親和的(`split_at_mut` 不要、単純なインデックスアクセス)。 +- **最適化ポイント**: `usize` 算術のみでスタック完結、ヒープアロケーションゼロ、ループ本体は比較1回+代入1回のみ + +--- + +以下に図解でアルゴリズムの動作を示します: + +![alt text](image.png) + +## 4. 実装コード + +```rust +// Runtime 0 ms +// Beats 100.00% +// Memory 2.29 MB +// Beats 25.52% +impl Solution { + pub fn merge(nums1: &mut Vec, m: i32, nums2: &mut Vec, n: i32) { + // i32 → usize への安全なキャスト(制約: 0 <= m, n <= 200) + let mut i = m as usize; // nums1 の有効要素末尾(排他) + let mut j = n as usize; // nums2 の末尾(排他) + let mut k = (m + n) as usize; // 書き込み位置末尾(排他) + + // 3ポインタを後ろから前へ動かしながらマージ + // + // 不変条件: k >= i が常に成立するため nums1 の未処理要素を上書きしない + // + // # Complexity + // - Time: O(m + n) — 各要素をちょうど1回だけ参照・書き込み + // - Space: O(1) — 追加ヒープアロケーションなし + while i > 0 && j > 0 { + k -= 1; + if nums1[i - 1] >= nums2[j - 1] { + nums1[k] = nums1[i - 1]; + i -= 1; + } else { + nums1[k] = nums2[j - 1]; + j -= 1; + } + } + + // nums2 の残余要素を nums1 の先頭へコピー + // (nums1 の残余は既に正しい位置にあるのでコピー不要) + while j > 0 { + k -= 1; + nums1[k] = nums2[j - 1]; + j -= 1; + } + } +} +``` + +--- + +## Rust 固有の最適化観点 + +**`usize` 算術によるゼロコスト安全性** + +LeetCode の引数は `i32` ですが、配列インデックスは `usize` が必要です。制約上 `0 <= m, n <= 200` かつ `1 <= m + n <= 200` が保証されるため `as usize` キャストは常に安全。`i -= 1` 等のデクリメントは `i > 0` ガード後にのみ実行されるため、`usize` アンダーフロー(パニック)は構造的に排除されています。 + +**借用規則との親和性** + +`nums1` と `nums2` を同時に `&mut` で保持しますが、それぞれ独立した `Vec` への借用であるため借用チェッカーを通過します。`split_at_mut` 等の特別な操作は不要です。 + +**ループ本体の最小化** + +比較1回・代入1回・デクリメント1回のみ。イテレータ抽象化より命令型ループが適切なケースで、コンパイラは確実にインライン展開・SIMD 最適化を適用できます。 diff --git a/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_TypeScript.md b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_TypeScript.md new file mode 100644 index 00000000..db82ecf2 --- /dev/null +++ b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/Merge_Sorted_Array_TypeScript.md @@ -0,0 +1,102 @@ +## 1. 問題の分析 + +**競技プログラミング視点での分析** + +Rustの回答と同様に、後ろからの3ポインタマージが最適解です。TypeScriptでは `number` 型が IEEE 754 倍精度浮動小数点のため整数演算に余分なオーバーヘッドはなく、インデックス操作はV8エンジンがSMI(Small Integer)最適化を適用します。 + +**業務開発視点での分析** + +LeetCode のシグネチャは `void` 返却の破壊的操作ですが、TypeScript の `strict` モードと `readonly` による境界を意識した実装で保守性を担保します。インデックス境界違反は実行時ではなくロジック上で構造的に防止します。 + +**TypeScript特有の考慮点** + +- `number` 型インデックスの減算は `0` ガード後にのみ実行し、負数インデックスアクセスを防止 +- `as const` は不要(可変配列操作のため) +- V8 の Packed SMI Arrays 最適化を維持するため、配列の型を混在させない + +--- + +## 2. アルゴリズムアプローチ比較 + +| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 | +| --------------------------- | ---------------- | ---------- | ------------ | -------- | ------ | -------------------------- | +| 後ろからマージ(3ポインタ) | O(m+n) | O(1) | 低 | 高 | 高 | 追加アロケーションなし ✅ | +| コピー後 `.sort()` | O((m+n)log(m+n)) | O(1) | 最低 | 高 | 最高 | 比較関数必須・競技では次善 | +| 一時バッファにコピー | O(m+n) | O(m) | 低 | 高 | 中 | スプレッド構文でヒープ確保 | + +--- + +## 3. 選択したアルゴリズムと理由 + +- **選択**: 後ろからの3ポインタマージ +- **理由**: O(m+n) 時間・O(1) 追加空間。末尾書き込みにより有効要素の上書きが構造的に不可能。TypeScript の `number` 型インデックスは整数として扱われ、V8 の SMI 最適化が全体に適用される +- **TypeScript特有の最適化ポイント**: 変数を `let` で宣言し型推論に委ねることでコンパイラの最適化ヒントを最大化。`const` で宣言すると再代入不可になるため、カウンタ変数は `let` が適切 + +--- + +## 4. 実装コード + +```typescript +// Runtime 0 ms +// Beats 100.00% +// Memory 54.72 MB +// Beats 93.68% + +/** + * 2つのソート済み配列を nums1 にインプレースでマージする。 + * 後ろから走査する3ポインタ法により、追加アロケーションなしで O(m+n) を実現。 + * + * @param nums1 - マージ先配列(長さ m+n、末尾 n 要素は 0 で埋められている) + * @param m - nums1 の有効要素数 + * @param nums2 - マージ元配列(長さ n) + * @param n - nums2 の要素数 + * + * @complexity Time: O(m + n), Space: O(1) + * + * 不変条件: k >= i が常に成立するため nums1 の未処理要素を上書きしない + * 証明: 各イテレーションで k と (i または j) が同時に 1 減少する。 + * 初期値 k = m + n - 1, i = m - 1 より k - i = n >= 0 が保持される。 + */ +function merge(nums1: number[], m: number, nums2: number[], n: number): void { + // 各ポインタは「次に参照すべき要素のインデックス」を表す(0-based) + let i: number = m - 1; // nums1 有効末尾 + let j: number = n - 1; // nums2 末尾 + let k: number = m + n - 1; // 書き込み位置末尾 + + // どちらかの配列が空になるまで大きい方を末尾から書き込む + while (i >= 0 && j >= 0) { + if (nums1[i] >= nums2[j]) { + nums1[k] = nums1[i]; + i--; + } else { + nums1[k] = nums2[j]; + j--; + } + k--; + } + + // nums2 の残余要素をコピー + // (nums1 の残余は既に正しい位置にあるため操作不要) + while (j >= 0) { + nums1[k] = nums2[j]; + j--; + k--; + } +} +``` + +--- + +## TypeScript固有の最適化観点 + +**V8 エンジンと配列最適化** + +`nums1` は `number[]` として渡されるため、V8 は内部的に `PACKED_SMI_ELEMENTS` または `PACKED_DOUBLE_ELEMENTS` として最適化されます。要素の書き込みを `nums1[k] = value` の単純代入に統一することで、この最適化が維持され `HOLEY_ELEMENTS` への降格を防ぎます。 + +**型安全インデックス管理** + +`i >= 0` / `j >= 0` のガードにより、TypeScript の `strict: true` 環境でも `number` 型のインデックスが負にならないことが実行フロー上保証されます。`noUncheckedIndexedAccess` を有効にした場合でも、ガード後の `nums1[i]` は `number` として安全にアクセスできます。 + +**`while` vs イテレータ** + +`.reduce()` や `.forEach()` はコールバック関数のクロージャ生成コストが発生します。本問題のようなインプレース操作かつ複数ポインタを連動させるケースでは、命令型 `while` ループが最も直接的で V8 の JIT 最適化が効きやすい選択です。 diff --git a/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README.md b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README.md new file mode 100644 index 00000000..44ea522f --- /dev/null +++ b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README.md @@ -0,0 +1,338 @@ +# Merge Sorted Array - In-place 3-Pointer Merge from the Right + +--- + +## 目次(Table of Contents) + +- [概要](#overview) +- [アルゴリズム要点 TL;DR](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python 実装](#impl) +- [CPython 最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +

概要

+ +### 問題要約 + +**LeetCode 88 – Merge Sorted Array** + +非減少順にソートされた 2 つの整数配列 `nums1`(有効要素数 `m`)と `nums2`(長さ `n`)を、 +`nums1` の末尾 `n` 要素のゼロ領域を利用して **インプレース** にマージし、 +結果を `nums1` に非減少順で格納する。 + +### 要件 + +| 項目 | 内容 | +| ------ | ---------------------------------------------------------------------------------------------------------------------- | +| 戻り値 | `None`(`nums1` を破壊的に変更) | +| 正当性 | 全要素が非減少順に並ぶこと | +| 安定性 | 等値要素の相対順序は問わない(仕様上要求なし) | +| 制約 | `nums1.length == m + n`, `nums2.length == n`, `0 ≤ m, n ≤ 200`, `1 ≤ m + n ≤ 200`, `-10^9 ≤ nums1[i], nums2[j] ≤ 10^9` | + +--- + +

アルゴリズム要点 TL;DR

+ +- **戦略**: `nums1` の末尾から走査する **3 ポインタ法**(後ろからマージ) +- **データ構造**: 追加配列不要。`nums1` 自体をバッファとして再利用 +- **時間計算量**: O(m + n) — 各要素をちょうど 1 回参照・書き込み +- **空間計算量**: O(1) — 追加ヒープアロケーションなし + (残余コピーのスライス代入は定数サイズの一時オブジェクト 1 個のみ) +- **核心の不変条件**: 書き込みポインタ `k` は常に読み取りポインタ `i` 以上 + → 未処理の `nums1` 要素を上書きしない +- **後ろから書く理由**: 前から比較すると `nums1` の有効要素を上書きしてしまうため逆順が安全 + +--- + +

図解

+ +### フローチャート + +```mermaid +flowchart TD + Start[Start merge] --> Init[Set i = m-1, j = n-1, k = m+n-1] + Init --> EarlyExit{n == 0} + EarlyExit -- Yes --> Done[Return immediately] + EarlyExit -- No --> Loop{i ≥ 0 and j ≥ 0} + Loop -- No --> Remain{j ≥ 0} + Loop -- Yes --> Cmp{nums1 i ≥ nums2 j} + Cmp -- Yes --> WriteN1[nums1 k = nums1 i, i -= 1] + Cmp -- No --> WriteN2[nums1 k = nums2 j, j -= 1] + WriteN1 --> DecK1[k -= 1] + WriteN2 --> DecK2[k -= 1] + DecK1 --> Loop + DecK2 --> Loop + Remain -- Yes --> Slice[nums1 0..j+1 = nums2 0..j+1] + Remain -- No --> Done2[Return] + Slice --> Done2 +``` + +> 後ろから比較して大きい方を末尾(`k`)に書き込む。どちらかが尽きたら `nums2` の残余を先頭スライスへ一括コピー。 + +--- + +### データフロー図(ステップ別状態遷移) + +```mermaid +graph LR + subgraph Initial_State + N1A["nums1: [1, 2, 3, 0, 0, 0] m=3"] + N2A["nums2: [2, 5, 6] n=3"] + end + subgraph Step1 + S1["k=5: compare nums1[2]=3 vs nums2[2]=6 write 6"] + end + subgraph Step2 + S2["k=4: compare nums1[2]=3 vs nums2[1]=5 write 5"] + end + subgraph Step3 + S3["k=3: compare nums1[2]=3 vs nums2[0]=2 write 3"] + end + subgraph Step4 + S4["k=2: compare nums1[1]=2 vs nums2[0]=2 write 2 from nums1"] + end + subgraph Step5 + S5["k=1: compare nums1[0]=1 vs nums2[0]=2 write 2 from nums2"] + end + subgraph Step6 + S6["j=-1 exhausted copy nums1 remaining in place"] + end + subgraph Result + R["nums1: [1, 2, 2, 3, 5, 6]"] + end + N1A --> S1 + N2A --> S1 + S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> R +``` + +> 各ステップで大きい方の値が `k` の位置に書き込まれ、対応するポインタと `k` が 1 ずつ減少する。 + +--- + +

正しさのスケッチ

+ +### 不変条件 + +書き込みポインタ `k` と `nums1` の読み取りポインタ `i` について、以下が常に成立する: + +``` +k - i = (m + n - 1 - step_count) - (m - 1 - i_decrements) + = n - j_decrements ≥ 0 +``` + +各イテレーションで `k` は必ず 1 減少し、`i` か `j` のどちらかも 1 減少する。 +`i` が減少するとき `k - i` は変わらず、`j` が減少するとき `k - i` は 1 増加する。 +よって `k ≥ i` が保持され、**未処理の `nums1` 要素を上書きしない**。 + +### 網羅性 + +- `while` ループ終了後、`i < 0` または `j < 0` のどちらかが成立 +- `j < 0` の場合:`nums2` は全て書き込み済み、`nums1` の残余は正しい位置にある(移動不要) +- `j ≥ 0` の場合:`nums2[:j+1]` を `nums1[:j+1]` にスライス代入して完了 + +### 基底条件(エッジケース) + +| 条件 | 処理 | +| -------- | --------------------------------------------- | +| `n == 0` | 早期リターン、`nums1` はそのまま | +| `m == 0` | `while` の `i >= 0` が即 False → 残余コピーへ | + +### 終了性 + +`i` と `j` はループごとに必ず 1 以上減少し、非負整数であるため有限ステップで終了する。 + +--- + +

計算量

+ +| 観点 | 計算量 | 説明 | +| ---------- | -------- | ------------------------------------------ | +| 時間計算量 | O(m + n) | 各要素を最大 1 回比較・書き込み | +| 空間計算量 | O(1) | スライス代入の一時オブジェクトは定数サイズ | + +### In-place vs Pure 比較 + +| 実装方針 | 時間 | 空間 | 備考 | +| ----------------------------- | ----------------- | -------- | --------------------------------------- | +| 本実装(後ろから 3 ポインタ) | O(m+n) | **O(1)** | ✅ 最適 | +| 前から + 一時バッファ | O(m+n) | O(m) | `nums1[:m]` を `list()` でコピー | +| `nums1 + nums2` 後 `.sort()` | O((m+n) log(m+n)) | O(1) | Timsort は C 実装で高速だが計算量は劣る | +| `heapq.merge` + スライス代入 | O(m+n) | O(m+n) | 一時リストが発生 | + +--- + +

Python 実装

+ +```python +from __future__ import annotations + +from typing import List + + +class Solution: + def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: + """ + 2 つのソート済み配列を nums1 にインプレースでマージする。 + + 後ろから走査する 3 ポインタ法により O(m+n) 時間・O(1) 追加空間を実現。 + + 不変条件: k >= i が常に成立するため nums1 の未処理要素を上書きしない。 + 初期値 k - i = n >= 0 が各イテレーションで保持される。 + + Args: + nums1: マージ先リスト(長さ m+n、末尾 n 要素は 0 で埋め済み) + m: nums1 の有効要素数 + nums2: マージ元リスト(長さ n) + n: nums2 の要素数 + + Returns: + None(nums1 を破壊的に変更) + + Time Complexity: O(m + n) + Space Complexity: O(1) + """ + # ── エッジケース: nums2 が空なら nums1 はそのまま ────────────────── + if n == 0: + return + + # ── 3 ポインタ初期化 ──────────────────────────────────────────────── + i: int = m - 1 # nums1 有効末尾ポインタ(読み取り) + j: int = n - 1 # nums2 末尾ポインタ(読み取り) + k: int = m + n - 1 # 書き込み位置ポインタ(後ろから前へ) + + # ── メインループ: 両配列が残っている間、大きい方を末尾へ書き込む ── + while i >= 0 and j >= 0: + if nums1[i] >= nums2[j]: + # nums1 の要素が大きい(または等しい)→ nums1[i] を書き込み + nums1[k] = nums1[i] + i -= 1 + else: + # nums2 の要素が大きい → nums2[j] を書き込み + nums1[k] = nums2[j] + j -= 1 + k -= 1 + + # ── 残余処理: nums2 が残っている場合のみスライス代入で一括コピー ── + # nums1 の残余は既に正しい位置にあるため移動不要 + if j >= 0: + # CPython の list_ass_slice は memmove 相当で逐次代入より高速 + nums1[: j + 1] = nums2[: j + 1] +``` + +--- + +

CPython 最適化ポイント

+ +### 1. スライス代入による残余コピーの高速化 + +```python +nums1[: j + 1] = nums2[: j + 1] +``` + +CPython の `list.__setitem__` スライス版は内部で `list_ass_slice` を呼び出し、 +`memmove` 相当の C レイヤーのメモリコピーが走る。 +逐次 `while j >= 0: nums1[k] = nums2[j]; j -= 1; k -= 1` より +**インタープリタのバイトコードディスパッチ回数を大幅に削減**できる。 + +### 2. LOAD_FAST によるローカル変数の高速アクセス + +| アクセス種別 | バイトコード | 速度 | +| ----------------------------------------------- | ------------- | -------------------- | +| ローカル変数(`i`, `j`, `k`, `nums1`, `nums2`) | `LOAD_FAST` | 高速(辞書探索なし) | +| グローバル変数 | `LOAD_GLOBAL` | やや低速 | +| 属性アクセス(`self.xxx`) | `LOAD_ATTR` | 最も低速 | + +本実装では全カウンタがメソッドのローカルスコープに収まるため、 +`LOAD_FAST` が全アクセスに適用される。 + +### 3. 早期リターンによる不要な処理の排除 + +```python +if n == 0: + return +``` + +`n == 0` の場合はループに入らず即リターン。 +条件チェックのオーバーヘッドが極めて小さい。 + +### 4. 型ヒントとランタイムの分離 + +`from __future__ import annotations` を使用することで、 +型アノテーションの評価が **遅延評価(文字列化)** になり、 +実行時のインポートオーバーヘッドを最小化する。 + +--- + +

エッジケースと検証観点

+ +| ケース | 入力例 | 期待出力 | 対応箇所 | +| ------------------------------------- | ------------------------------------------- | -------------- | -------------------------------------- | +| `nums2` が空(`n=0`) | `nums1=[1], m=1, nums2=[], n=0` | `[1]` | 早期 `return` | +| `nums1` が空(`m=0`) | `nums1=[0], m=0, nums2=[1], n=1` | `[1]` | `while i>=0` が即 False → 残余コピー | +| 全要素が同値 | `nums1=[2,2,0,0], m=2, nums2=[2,2], n=2` | `[2,2,2,2]` | `>=` の分岐で正しく処理 | +| `nums2` の全要素が `nums1` より大きい | `nums1=[1,2,0,0], m=2, nums2=[3,4], n=2` | `[1,2,3,4]` | `while` 終了後 `j < 0` → 何もしない | +| `nums1` の全要素が `nums2` より大きい | `nums1=[3,4,0,0], m=2, nums2=[1,2], n=2` | `[1,2,3,4]` | `while` 終了後 `j >= 0` → スライス代入 | +| 負の数を含む | `nums1=[-3,-1,0,0], m=2, nums2=[-2,0], n=2` | `[-3,-2,-1,0]` | 整数比較なので問題なし | +| 最大制約 | `m=n=100` | 正しくマージ | O(m+n) で余裕 | + +### 静的解析観点(Pylance strict mode) + +- `List[int]` 型ヒントにより `nums1[k]` への `int` 代入が型安全 +- `-> None` 明示により暗黙的な `None` 返却が警告なし +- `from __future__ import annotations` で前方参照の問題を回避 + +--- + +

FAQ

+ +**Q1. なぜ前からではなく後ろからマージするのか?** + +前からマージすると、`nums1` の有効要素(インデックス `0..m-1`)を上書きしてしまう。 +後ろからマージすれば書き込み位置 `k` が常に `i` 以上になるため、 +未処理の要素を破壊することなくインプレース操作が成立する。 + +**Q2. `nums1` の残余(`i >= 0`)もコピーが必要では?** + +不要。`nums1` の残余要素は既に正しい位置(`nums1[0..i]`)にあり、 +後ろからマージ済みの要素と連続しているため、移動の必要がない。 + +**Q3. スライス代入 `nums1[:j+1] = nums2[:j+1]` で一時オブジェクトは発生しないのか?** + +右辺 `nums2[:j+1]` がリストスライスとして一時的に生成される(O(j) サイズ)。 +ただし `j` は最大でも `n-1` であり定数スケールのため、実質 O(1) の追加コスト。 +逐次代入に比べてバイトコードのオーバーヘッドを削減できるため、実用上は高速。 + +**Q4. `.sort()` を使う方法ではダメなのか?** + +```python +# 次善策: O((m+n) log(m+n)) だが実装が最短 +nums1[m:] = nums2[:n] +nums1.sort() +``` + +正しく動作するが、時間計算量が O((m+n) log(m+n)) に劣化する。 +フォローアップ要件「O(m+n) で解け」を満たさない。 +競技では時間制限に収まるケースが多いが、最適解ではない。 + +**Q5. `heapq.merge` を使う方法は?** + +```python +import heapq +nums1[m:] = nums2 # 末尾をコピー +merged = list(heapq.merge(nums1[:m], nums2)) +nums1[:] = merged +``` + +O(m+n) だが一時リストが O(m+n) の追加空間を消費する。 +空間計算量の観点で本実装(O(1))に劣る。 + +**Q6. 等値要素の扱いはどうなるか?** + +`nums1[i] >= nums2[j]` の条件により、等値の場合は `nums1` 側が優先される。 +問題仕様上、安定性(相対順序の保持)は要求されていないため、どちらでも正解。 diff --git a/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html new file mode 100644 index 00000000..05c3723f --- /dev/null +++ b/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html @@ -0,0 +1,1433 @@ + + + + + + LeetCode 88 – Merge Sorted Array + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+ +
+
+
+ O(m+n) +
+
時間計算量
+
+
+
O(1)
+
追加空間
+
+
+
+ 3 Pointers +
+
使用ポインタ数
+
+
+
+ In-place +
+
操作種別
+
+
+ +
+
+

問題設定

+

+ ソート済み配列 + nums1(有効要素 + m + 個、末尾 + n + 個はゼロ埋め)と + nums2n + 個)を + nums1 + にインプレースでマージし、非減少順に並べる。 +

+
+

入力

+

+ nums1 = [1,2,3,0,0,0], m = 3 +

+

nums2 = [2,5,6], n = 3

+

出力

+

[1, 2, 2, 3, 5, 6]

+
+
+
+

核心アイデア

+
    +
  • + + 後ろから書く:前から書くと有効要素を上書きするため、末尾 + k = m+n-1 + から書き込む +
  • +
  • + + 不変条件 k ≥ i:書き込みポインタが読み取りポインタを常に追い越さないため、未処理要素を上書きしない +
  • +
  • + + 残余は nums2 のみ:nums1 の残余は既に正しい位置にある。nums2 + の残りのみスライス代入で処理 +
  • +
+
+
+
+ + +
+

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

+
+
+ + +
+

+ 実装コード +

+
+
+ + +
+

+ 処理フローチャート +

+
+ + + + + + + + + + + + + + + + + + + + + 開始 + + + + + + + + ポインタを初期化 + + + i = m-1 | j = n-1 | k = m+n-1 + + + + + + + + i ≥ 0 かつ j ≥ 0 ? + + + + + YES + + + + + + NO + + + + + + + + + 比較・書き込み + + + + if nums1[i] ≥ nums2[j]: + + + nums1[k] = nums1[i]; i-- + + + else: nums1[k] = nums2[j]; j-- + + + + + + + + k-- + + + + + + ループバック + + + + + + nums2 残余スライスコピー + + + nums1[:j+1] = nums2[:j+1] + + + + + + + + 終了 + + +
+
+ フロー説明
+ 1. 初期化:i = m-1(nums1 有効末尾)、j = n-1(nums2 末尾)、k + = m+n-1(書き込み末尾)
+ 2. ループ判定(黄ダイヤ):i≥0 かつ j≥0 + の間ループ。どちらかが尽きたら + NO で残余処理へ
+ 3. 比較・書き込み:nums1[i] と nums2[j] を比較し大きい方を + nums1[k] に書き込み、対応するポインタを減算
+ 4. k-- 後に紫破線でループ条件へ戻る(不変条件 k ≥ i を維持)
+ 5. 残余コピー:nums2 が残っていれば CPython の memmove + 相当のスライス代入で高速一括コピー +
+
+ + +
+

+ 計算量分析 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ アプローチ + + 時間計算量 + + 空間計算量 + + 備考 +
+ 後ろから 3 ポインタ ✅ + O(m+n)O(1) + 最適解。追加アロケーションなし +
+ コピー後 .sort() + O((m+n) log(m+n))O(1)実装最短だが計算量で劣る
+ 一時バッファ使用 + O(m+n)O(m) + 前からマージ可能だが空間を消費 +
heapq.mergeO(m+n)O(m+n)一時リスト生成あり
+
+
+ 不変条件の証明: + 初期値 + k - i = n ≥ 0。 各イテレーションで k は必ず 1 減少し、i か j のどちらかも 1 減少する。 i + 減少時は k - i が不変、j 減少時は k - i が 1 増加。 + よって k ≥ i が常に成立し、未処理の nums1 要素を上書きしない。 +
+
+
+ + + + + + diff --git a/public/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html b/public/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html new file mode 100644 index 00000000..b7d35ac0 --- /dev/null +++ b/public/Algorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html @@ -0,0 +1,1433 @@ + + + + + + LeetCode 88 – Merge Sorted Array + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+ +
+
+
+ O(m+n) +
+
時間計算量
+
+
+
O(1)
+
追加空間
+
+
+
+ 3 Pointers +
+
使用ポインタ数
+
+
+
+ In-place +
+
操作種別
+
+
+ +
+
+

問題設定

+

+ ソート済み配列 + nums1(有効要素 + m + 個、末尾 + n + 個はゼロ埋め)と + nums2n + 個)を + nums1 + にインプレースでマージし、非減少順に並べる。 +

+
+

入力

+

+ nums1 = [1,2,3,0,0,0], m = 3 +

+

nums2 = [2,5,6], n = 3

+

出力

+

[1, 2, 2, 3, 5, 6]

+
+
+
+

核心アイデア

+
    +
  • + + 後ろから書く:前から書くと有効要素を上書きするため、末尾 + k = m+n-1 + から書き込む +
  • +
  • + + 不変条件 k ≥ i:書き込みポインタが読み取りポインタを常に追い越さないため、未処理要素を上書きしない +
  • +
  • + + 残余は nums2 のみ:nums1 の残余は既に正しい位置にある。nums2 + の残りのみスライス代入で処理 +
  • +
+
+
+
+ + +
+

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

+
+
+ + +
+

+ 実装コード +

+
+
+ + +
+

+ 処理フローチャート +

+
+ + + + + + + + + + + + + + + + + + + + + 開始 + + + + + + + + ポインタを初期化 + + + i = m-1 | j = n-1 | k = m+n-1 + + + + + + + + i ≥ 0 かつ j ≥ 0 ? + + + + + YES + + + + + + NO + + + + + + + + + 比較・書き込み + + + + if nums1[i] ≥ nums2[j]: + + + nums1[k] = nums1[i]; i-- + + + else: nums1[k] = nums2[j]; j-- + + + + + + + + k-- + + + + + + ループバック + + + + + + nums2 残余スライスコピー + + + nums1[:j+1] = nums2[:j+1] + + + + + + + + 終了 + + +
+
+ フロー説明
+ 1. 初期化:i = m-1(nums1 有効末尾)、j = n-1(nums2 末尾)、k + = m+n-1(書き込み末尾)
+ 2. ループ判定(黄ダイヤ):i≥0 かつ j≥0 + の間ループ。どちらかが尽きたら + NO で残余処理へ
+ 3. 比較・書き込み:nums1[i] と nums2[j] を比較し大きい方を + nums1[k] に書き込み、対応するポインタを減算
+ 4. k-- 後に紫破線でループ条件へ戻る(不変条件 k ≥ i を維持)
+ 5. 残余コピー:nums2 が残っていれば CPython の memmove + 相当のスライス代入で高速一括コピー +
+
+ + +
+

+ 計算量分析 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ アプローチ + + 時間計算量 + + 空間計算量 + + 備考 +
+ 後ろから 3 ポインタ ✅ + O(m+n)O(1) + 最適解。追加アロケーションなし +
+ コピー後 .sort() + O((m+n) log(m+n))O(1)実装最短だが計算量で劣る
+ 一時バッファ使用 + O(m+n)O(m) + 前からマージ可能だが空間を消費 +
heapq.mergeO(m+n)O(m+n)一時リスト生成あり
+
+
+ 不変条件の証明: + 初期値 + k - i = n ≥ 0。 各イテレーションで k は必ず 1 減少し、i か j のどちらかも 1 減少する。 i + 減少時は k - i が不変、j 減少時は k - i が 1 増加。 + よって k ≥ i が常に成立し、未処理の nums1 要素を上書きしない。 +
+
+
+ + + + + + diff --git a/public/index.html b/public/index.html index 464fb782..ef6224d4 100644 --- a/public/index.html +++ b/public/index.html @@ -416,7 +416,7 @@

🧪 Algorithm Study Index

-

164 interactive lessons across 6 domains

+

165 interactive lessons across 6 domains

@@ -431,9 +431,9 @@

- + @@ -473,6 +473,7 @@

  • 🧩LeetCode 6: Zigzag Conversion - 周期式による行別直接抽出Algorithm/Other/leetcode/6. Zigzag Conversion/Claude/README.html
  • 🧩LeetCode 70 - Climbing Stairs | フィボナッチDPAlgorithm/DynamicProgramming/leetcode/70. Climbing Stairs/README_react.html
  • 🧩LeetCode 7: Reverse Integer - 文字列反転法Algorithm/Other/leetcode/7. Reverse Integer/claude/README.html
  • +
  • 🧩LeetCode 88 – Merge Sorted ArrayAlgorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html
  • 🧩LeetCode 93: Restore IP Addresses - DFS + 枝刈り解説Algorithm/Backtracking/leetcode/93. Restore IP Addresses/Claude/README.html
  • 🧩LeetCode 96: Unique Binary Search Trees - カタラン数解説Algorithm/BinarySearch/leetcode/96. Unique Binary Search Trees/claude 4.5 sonnet/README_react.html
  • 🧩LeetCode 97: Interleaving String - 1D DP解説Algorithm/DynamicProgramming/leetcode/97. Interleaving String/Claude Sonnet 4.5/README_React.html
  • @@ -644,6 +645,7 @@

  • 🧩LeetCode 6: Zigzag Conversion - 周期式による行別直接抽出Algorithm/Other/leetcode/6. Zigzag Conversion/Claude/README.html
  • 🧩LeetCode 70 - Climbing Stairs | フィボナッチDPAlgorithm/DynamicProgramming/leetcode/70. Climbing Stairs/README_react.html
  • 🧩LeetCode 7: Reverse Integer - 文字列反転法Algorithm/Other/leetcode/7. Reverse Integer/claude/README.html
  • +
  • 🧩LeetCode 88 – Merge Sorted ArrayAlgorithm/Sort/MergeSort/leetcode/claude 4.6 sonnet extended/88. Merge Sorted Array/README_React.html
  • 🧩LeetCode 93: Restore IP Addresses - DFS + 枝刈り解説Algorithm/Backtracking/leetcode/93. Restore IP Addresses/Claude/README.html
  • 🧩LeetCode 96: Unique Binary Search Trees - カタラン数解説Algorithm/BinarySearch/leetcode/96. Unique Binary Search Trees/claude 4.5 sonnet/README_react.html
  • 🧩LeetCode 97: Interleaving String - 1D DP解説Algorithm/DynamicProgramming/leetcode/97. Interleaving String/Claude Sonnet 4.5/README_React.html
  • @@ -811,7 +813,7 @@

    🧪 - Generated on 2026-03-11 + Generated on 2026-03-14