diff --git a/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/Claude/README.html b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/Claude/README.html new file mode 100644 index 0000000..013a851 --- /dev/null +++ b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/Claude/README.html @@ -0,0 +1,1637 @@ + + + + + + Median of Two Sorted Arrays - 二分探索パーティション法 + + + + + + + + + + + +
+ +
+

+ Median of Two Sorted Arrays +

+

+ 二分探索パーティション法による O(log min(m,n)) 実装 +

+ + +
+ + +
+

+ 📋 アルゴリズム概要 +

+

+ 2つのソート済み配列 + nums1 と + nums2 + が与えられたとき、それらをマージした際の中央値を求める問題です。 + 要件として + O(log(m+n)) の時間計算量が求められています。 +

+

+ 戦略:短い配列に対して二分探索を行い、両配列を「左半分」と「右半分」に分割するパーティション点を探します。 + 正しいパーティションでは、左半分の最大値 ≤ + 右半分の最小値が成立します。 +

+
+

+ 主要ポイント +

+
    +
  • 手法:二分探索パーティション法
  • +
  • 時間計算量:O(log min(m, n))
  • +
  • 空間計算量:O(1)
  • +
  • + 最適化:整数センチネル使用、float変換は最終結果のみ +
  • +
+
+
+ + +
+

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

+
+
+ + +
+

💻 Python実装

+
from __future__ import annotations
+
+from typing import Final, List
+
+
+class Solution:
+    """
+    Median of Two Sorted Arrays
+    - Time:  O(log(min(m, n)))
+    - Space: O(1)
+    速度・メモリ最適化版(二分探索パーティション法)
+    """
+
+    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
+        """
+        2つのソート済み配列の中央値を計算する。
+
+        Args:
+            nums1: 非減少順の整数配列(長さ 0..1000)
+            nums2: 非減少順の整数配列(長さ 0..1000)
+
+        Returns:
+            中央値(偶数長の場合は中央2要素の平均)
+        """
+        # --- 短い配列をAにする(探索範囲を最小化) ---
+        if len(nums1) > len(nums2):
+            nums1, nums2 = nums2, nums1
+
+        A: List[int] = nums1
+        B: List[int] = nums2
+        a_len: int = len(A)
+        b_len: int = len(B)
+
+        # 総数と奇偶を事前計算(ループ内の分岐削減)
+        total: int = a_len + b_len
+        total_is_odd: bool = (total & 1) == 1
+        half: int = (total + 1) >> 1  # 左側に含める要素数
+
+        # 整数センチネル(制約±1e6を超える値)
+        NEG: Final[int] = -10_000_007
+        POS: Final[int] = +10_000_007
+
+        lo: int = 0
+        hi: int = a_len
+
+        # --- 二分探索によるパーティション点の探索 ---
+        while lo <= hi:
+            i: int = (lo + hi) >> 1  # Aの左パート長
+            j: int = half - i         # Bの左パート長
+
+            # 境界値の取得(範囲外はセンチネル)
+            a_left: int = NEG if i == 0 else A[i - 1]
+            a_right: int = POS if i == a_len else A[i]
+            b_left: int = NEG if j == 0 else B[j - 1]
+            b_right: int = POS if j == b_len else B[j]
+
+            # パーティション条件のチェック
+            if a_left <= b_right and b_left <= a_right:
+                # 正しいパーティションを発見
+                if total_is_odd:
+                    # 奇数長:左側の最大値が中央値
+                    return float(a_left if a_left > b_left else b_left)
+                # 偶数長:左側最大と右側最小の平均
+                left_max: int = a_left if a_left > b_left else b_left
+                right_min: int = a_right if a_right < b_right else b_right
+                return (left_max + right_min) * 0.5
+
+            # パーティション調整
+            if a_left > b_right:
+                # Aの左が大きすぎる → Aの左パートを減らす
+                hi = i - 1
+            else:
+                # Aの右が大きすぎる → Aの左パートを増やす
+                lo = i + 1
+
+        # 入力が正しければここには到達しない
+        return 0.0
+
+ + +
+

+ 📊 視覚的図解・フローチャート +

+ + + + + + + + + + + 開始 + + + + + + + len(nums1) > + + + len(nums2)? + + + + + + Yes + + + + 配列を入れ替え + + + (nums1, nums2) + + + + + + No + + + + + 初期化 + + + A=nums1, B=nums2 + + + + + + + half, total計算 + + + total_is_odd判定 + + + + + + + lo=0, hi=len(A) + + + + + + + lo <= hi? + + + + + + Yes + + + + i = (lo+hi)>>1 + + + j = half - i + + + + + + + 境界値取得 + + + a_left, a_right, b_left, b_right + + + + + + + a_left <= b_right + + + AND b_left <= a_right? + + + + + + Yes + + + + 中央値を返す + + + + + + No + + + + 調整 + + + + + + +

+ フローの説明:
+ 1. 短い配列をAにする(探索範囲を最小化)
+ 2. 中央値を求めるための半分の長さ(half)を計算
+ 3. 二分探索でパーティション点iを探索
+ 4. パーティション点jを自動的に決定(j = half - i)
+ 5. 境界値を取得してパーティション条件をチェック
+ 6. 条件を満たせば中央値を返す、満たさなければiを調整して再探索 +

+
+ + +
+

⚡ 計算量説明

+
+

+ 時間計算量 +

+ O(log min(m, n)) +

+ 短い配列に対する二分探索のみを行うため、探索回数は短い配列の長さの対数に比例します。 + 各ステップは定数時間の比較と計算のみです。 +

+
+ +
+

+ 空間計算量 +

+ O(1) +

+ 固定個数のローカル変数のみを使用します。配列のマージやコピーは不要で、 + 入力サイズに依存する追加メモリは確保しません。 +

+
+ +
+

+ 最適化の比較 +

+ + + + + + + + + + + + + + + + + + + + + +
手法時間空間
+ 二分探索パーティション(本実装) + O(log min(m,n))O(1)
マージ後ソートO((m+n)log(m+n))O(m+n)
2ポインタ線形走査O(m+n)O(1)
+
+
+
+ + + + + + + + + + + + diff --git a/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/Claude/README.md b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/Claude/README.md new file mode 100644 index 0000000..8c3bd81 --- /dev/null +++ b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/Claude/README.md @@ -0,0 +1,334 @@ +# Median of Two Sorted Arrays - 二分探索パーティション法 + +

目次

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

概要

+ +**問題要約**:2 つのソート済み配列 `nums1` と `nums2` が与えられたとき、それらをマージした際の中央値を求める。 + +**要件**: + +- **時間計算量**: `O(log (m+n))` を満たす必要がある +- **空間計算量**: `O(1)` が望ましい(マージ配列を作らない) +- **制約**: + - `0 <= m, n <= 1000` + - `1 <= m + n <= 2000` + - `-10^6 <= nums1[i], nums2[i] <= 10^6` + +**戦略**:短い配列に対して二分探索を行い、両配列を「左半分」と「右半分」に分割するパーティション点を探す。正しいパーティションでは、左半分の最大値 ≤ 右半分の最小値が成立する。 + +--- + +

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

+ +- **手法**: 二分探索パーティション法 +- **データ構造**: 配列へのインデックスアクセスのみ(追加構造不要) +- **時間計算量**: `O(log min(m, n))` +- **空間計算量**: `O(1)` +- **メモリ最適化**: 整数センチネル使用、float 変換は最終結果のみ + +**アルゴリズムの流れ**: + +1. 短い配列を `A`、長い配列を `B` とする +2. `A` の分割点 `i` を二分探索(`0 <= i <= len(A)`) +3. `B` の分割点 `j = half - i`(`half = (m+n+1)//2`) +4. 左側最大 ≤ 右側最小なら正解 +5. 条件を満たさなければ `i` を調整して再探索 + +--- + +

図解

+ +## フローチャート + +```mermaid +flowchart TD + Start[Start findMedianSortedArrays] --> Swap{Is len nums1 > len nums2} + Swap -- Yes --> SwapArrays[Swap nums1 and nums2] + Swap -- No --> Init[Initialize A B a_len b_len] + SwapArrays --> Init + Init --> CalcHalf[Calculate half total total_is_odd] + CalcHalf --> InitBinary[Set lo 0 hi a_len] + InitBinary --> Loop{While lo <= hi} + Loop -- No --> NotFound[Return 0.0 unreachable] + Loop -- Yes --> CalcI[Calculate i mid point] + CalcI --> CalcJ[Calculate j half minus i] + CalcJ --> GetBounds[Get a_left a_right b_left b_right] + GetBounds --> CheckPartition{a_left <= b_right AND b_left <= a_right} + CheckPartition -- No --> AdjustPartition{a_left > b_right} + AdjustPartition -- Yes --> DecreaseI[hi i minus 1] + AdjustPartition -- No --> IncreaseI[lo i plus 1] + DecreaseI --> Loop + IncreaseI --> Loop + CheckPartition -- Yes --> CheckOdd{total_is_odd} + CheckOdd -- Yes --> ReturnOdd[Return max a_left b_left as float] + CheckOdd -- No --> ReturnEven[Return avg of left_max and right_min] + ReturnOdd --> End[End] + ReturnEven --> End +``` + +**説明**: 短い配列に対して二分探索を行い、パーティション条件を満たす分割点を探索する。条件を満たしたら、奇数長なら左側最大値、偶数長なら左側最大と右側最小の平均を返す。 + +## データフロー図 + +```mermaid +graph LR + subgraph Input + A[nums1 nums2] + end + subgraph Preprocess + A --> B[Ensure A is shorter] + B --> C[Calculate half and parity] + end + subgraph BinarySearch + C --> D[Partition point i on A] + D --> E[Derive j on B] + E --> F[Check partition validity] + end + subgraph Output + F --> G{Valid partition} + G -- Yes --> H[Compute median] + G -- No --> D + H --> I[Return float result] + end +``` + +**説明**: 入力配列を前処理し、二分探索で有効なパーティション点を見つけ、中央値を計算する流れ。 + +--- + +

正しさのスケッチ

+ +**不変条件**: + +- パーティション点 `i` に対し、`j = half - i` とすると、左側には全体の半分(切り上げ)の要素が含まれる +- 正しいパーティションでは `a_left <= b_right` かつ `b_left <= a_right` + +**網羅性**: + +- 二分探索は `[0, len(A)]` 全体を探索するため、正解のパーティション点は必ず探索範囲に含まれる +- 境界ケース(`i=0` や `i=len(A)`)はセンチネル値で統一的に処理 + +**基底条件**: + +- `lo > hi` になれば探索終了(通常は到達しない) +- 正しいパーティションが見つかれば即座に結果を返す + +**終了性**: + +- 二分探索は毎回探索範囲を半分にするため、最大 `O(log len(A))` 回で終了 +- パーティション条件により、各ステップで探索方向が一意に決まる + +--- + +

計算量

+ +**時間計算量**: `O(log min(m, n))` + +- 短い配列に対する二分探索のみ +- 各ステップは定数時間の比較と計算 + +**空間計算量**: `O(1)` + +- 固定個数のローカル変数のみ使用 +- 配列のマージやコピーは不要 + +**最適化の比較**: + +| 手法 | 時間 | 空間 | 特徴 | +| -------------------------------- | ---------------- | ------ | ---------------------- | +| 二分探索パーティション(本実装) | O(log min(m,n)) | O(1) | 最適解、整数演算のみ | +| マージ後ソート | O((m+n)log(m+n)) | O(m+n) | 単純だが非効率 | +| 2 ポインタ線形走査 | O(m+n) | O(1) | 計算量要件を満たさない | + +--- + +

Python実装

+ +```python +from __future__ import annotations + +from typing import Final, List + + +class Solution: + """ + Median of Two Sorted Arrays + - Time: O(log(min(m, n))) + - Space: O(1) + 速度・メモリ最適化版(二分探索パーティション法) + """ + + def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: + """ + 2つのソート済み配列の中央値を計算する。 + + Args: + nums1: 非減少順の整数配列(長さ 0..1000) + nums2: 非減少順の整数配列(長さ 0..1000) + + Returns: + 中央値(偶数長の場合は中央2要素の平均) + """ + # --- 短い配列をAにする(探索範囲を最小化) --- + if len(nums1) > len(nums2): + nums1, nums2 = nums2, nums1 + + A: List[int] = nums1 + B: List[int] = nums2 + a_len: int = len(A) + b_len: int = len(B) + + # 総数と奇偶を事前計算(ループ内の分岐削減) + total: int = a_len + b_len + total_is_odd: bool = (total & 1) == 1 + half: int = (total + 1) >> 1 # 左側に含める要素数 + + # 整数センチネル(制約±1e6を超える値) + NEG: Final[int] = -10_000_007 + POS: Final[int] = +10_000_007 + + lo: int = 0 + hi: int = a_len + + # --- 二分探索によるパーティション点の探索 --- + while lo <= hi: + i: int = (lo + hi) >> 1 # Aの左パート長 + j: int = half - i # Bの左パート長 + + # 境界値の取得(範囲外はセンチネル) + a_left: int = NEG if i == 0 else A[i - 1] + a_right: int = POS if i == a_len else A[i] + b_left: int = NEG if j == 0 else B[j - 1] + b_right: int = POS if j == b_len else B[j] + + # パーティション条件のチェック + if a_left <= b_right and b_left <= a_right: + # 正しいパーティションを発見 + if total_is_odd: + # 奇数長:左側の最大値が中央値 + return float(a_left if a_left > b_left else b_left) + # 偶数長:左側最大と右側最小の平均 + left_max: int = a_left if a_left > b_left else b_left + right_min: int = a_right if a_right < b_right else b_right + return (left_max + right_min) * 0.5 + + # パーティション調整 + if a_left > b_right: + # Aの左が大きすぎる → Aの左パートを減らす + hi = i - 1 + else: + # Aの右が大きすぎる → Aの左パートを増やす + lo = i + 1 + + # 入力が正しければここには到達しない + return 0.0 +``` + +**実装の主要ステップ**: + +1. **配列スワップ**: 短い配列を `A` にして探索範囲を最小化 +2. **パラメータ計算**: `half`(左側要素数)と `total_is_odd`(奇偶判定) +3. **二分探索**: `i` を動かして正しいパーティションを探索 +4. **境界処理**: センチネル値で境界ケースを統一的に処理 +5. **中央値計算**: 奇数長なら左最大、偶数長なら左最大と右最小の平均 + +--- + +

CPython最適化ポイント

+ +**整数演算の徹底**: + +- ループ内では全て `int` 比較を使用 +- `float` 変換は最終結果のみ(型変換コストを最小化) +- センチネルも `int` で統一(`float('inf')` を回避) + +**分岐予測の最適化**: + +- `max/min` 関数呼び出しを避け、三項演算子で直接比較 +- `total_is_odd` を事前計算してループ内の条件評価を削減 + +**メモリアクセスの効率化**: + +- ローカル変数への束縛で属性参照を削減 +- 一時オブジェクト(タプル、リスト)の生成ゼロ + +**ビット演算の活用**: + +- `(total & 1) == 1` で奇数判定 +- `>> 1` でビットシフト除算(`// 2` より高速なケースが多い) + +**関数呼び出しの削減**: + +- ヘルパー関数を使わず、ホットパスを単一ループ内に集約 + +--- + +

エッジケースと検証観点

+ +**境界ケース**: + +- **片方が空配列**: `nums1=[]`, `nums2=[1]` → センチネルで自然に処理 +- **両方が単一要素**: `nums1=[1]`, `nums2=[2]` → 平均を返す +- **極端な長さ差**: `m=1, n=1000` → 短い方で探索するため効率的 +- **全て同値**: `nums1=[5,5,5]`, `nums2=[5,5]` → 正しく `5.0` を返す + +**値の範囲**: + +- **最小値**: `-10^6` → センチネル `-10^7` で対応 +- **最大値**: `10^6` → センチネル `10^7` で対応 +- **負数のみ**: `nums1=[-5,-3]`, `nums2=[-4,-1]` → 正しく処理 + +**奇数・偶数長**: + +- **奇数合計**: `m=2, n=3` → 左側最大を返す +- **偶数合計**: `m=2, n=2` → 中央 2 要素の平均 + +**型安全性**: + +- 入力は `List[int]`、出力は `float` +- pylance で型エラーなし + +--- + +

FAQ

+ +**Q1: なぜマージせずに中央値を求められるのか?** + +A: パーティション法では、「左半分の最大値 ≤ 右半分の最小値」という条件を満たす分割点を探す。この条件が成立すれば、実際にマージしなくても中央値を決定できる。 + +**Q2: なぜ短い配列で探索するのか?** + +A: 二分探索の計算量は `O(log 探索範囲)` なので、短い配列で探索することで `O(log min(m,n))` を達成できる。長い配列で探索すると `O(log max(m,n))` になり非効率。 + +**Q3: センチネル値が `-10^7` と `10^7` で十分な理由は?** + +A: 制約により入力値は `-10^6` から `10^6` の範囲。センチネルはこの範囲外であれば良いため、`±10^7` で安全に処理できる。 + +**Q4: `float('inf')` を使わない理由は?** + +A: ループ内で `int` と `float('inf')` を比較すると、内部で型変換が発生してコストがかかる。整数センチネルを使えば全て `int` 比較で済み、パフォーマンスが向上する。 + +**Q5: 偶数長で平均を取る際に `/ 2.0` ではなく `* 0.5` を使う理由は?** + +A: 乗算は除算より一般的に高速(CPython では顕著ではないが、慣習として最適化)。ただし、可読性を優先するなら `/ 2.0` でも問題ない。 + +**Q6: 入力配列がソートされていない場合は?** + +A: LeetCode の制約では入力がソート済みであることが保証されている。実際の業務では、事前にソート状態を検証する `_validate_non_decreasing` のようなヘルパーを追加すべき。 + +**Q7: 空間計算量が本当に O(1) か?** + +A: はい。固定個数のローカル変数(`i`, `j`, `a_left` など)のみを使用し、入力サイズに依存する追加メモリは確保しない。 diff --git a/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.js b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.js new file mode 100644 index 0000000..2079f7c --- /dev/null +++ b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.js @@ -0,0 +1,189 @@ +// ## 1. 問題の分析 + +// ### 競技プログラミング視点 + +// * 目標計算量は **O(log (m+n))**。2 配列の **二分探索 + パーティション法**(片方の配列に対する二分探索)で到達可能。 +// * 片方(短い方)だけを探索し、もう片方の境界は計算で決まるため **O(1) 追加メモリ**。 +// * 空配列/サイズ差が極端/重複値/負数などの境界条件を網羅してもロジックは崩れない。 + +// ### 業務開発視点 + +// * 可読性:`leftMax/rightMin` を明示命名、境界は `±Infinity` を用いて分岐最小化。 +// * 例外方針:型・長さ・値域・ソート順の検証を **早期に一度だけ** 実施。違反は `TypeError` / `RangeError` で即時 throw。 +// * 関数は **Pure**(外部 I/O なし、引数の破壊なし)でテスト容易性を担保。 + +// ### JavaScript特有の考慮 + +// * V8 最適化:単純な `for` 走査、数値単型を維持、クロージャ生成を避ける。 +// * GC 対策:一時配列を作らない(マージしない)、オブジェクト割当は最小。 +// * 配列操作:`shift/unshift` 非使用、`sort` も使用しない(コスト/型崩れ回避)。 + +// --- + +// ## 2. アルゴリズムアプローチ比較 + +// | アプローチ | 時間計算量 | 空間計算量 | JS実装コスト | 可読性 | 備考 | +// | -------------------- | -------------------- | -----: | ------: | --- | -------------- | +// | 方法A: 二分探索パーティション(採用) | **O(log min(m, n))** | O(1) | 中 | 中 | 目標計算量を満たす標準解 | +// | 方法B: マージして中央値まで走査 | O(m+n) | O(1) | 低 | 高 | 単純・速記可だが計算量が劣る | +// | 方法C: 連結して `.sort()` | O((m+n) log(m+n)) | O(m+n) | 低 | 高 | 追加メモリ大・遅い(不採用) | + +// ※「JS実装コスト」は型安定性・境界処理の複雑さを含む目安。 + +// --- + +// ## 3. 選択したアルゴリズムと理由 + +// * **選択**: 方法A(二分探索パーティション) +// * **理由**: + +// * 問題要件の **O(log (m+n))** を満たす唯一の代表解。 +// * 追加メモリ O(1)・分岐が少なくバグ耐性が高い。 +// * **JS最適化ポイント**: + +// * `for` ループのみで検証、例外はホットパス外で早期判定。 +// * `Number.isFinite` で数値単型維持、`±Infinity` で境界分岐削減。 +// * 一切の一時配列/`sort` を使わない。 + +// --- + +// ## 4. コード実装(solution.js) + +// ```js +"use strict"; +// Module System: CommonJS +// Runtime: Node.js v18.x +// External Libraries: Not allowed + +/** + * LeetCode: Median of Two Sorted Arrays + * 二分探索パーティションによる O(log (m+n)) 解法(Pure Function) + * + * @param {number[]} nums1 - 昇順ソート済み配列(長さ 0..1000, 値域 [-1e6, 1e6]) + * @param {number[]} nums2 - 昇順ソート済み配列(長さ 0..1000, 値域 [-1e6, 1e6]) + * @returns {number} 中央値(偶数個のときは平均値) + * + * @throws {TypeError} 引数が配列でない / 非数値を含む + * @throws {RangeError} 長さ制約違反 / 値域違反 / 非昇順 + * + * @complexity Time O(log(min(m,n))), Space O(1) + */ +function findMedianSortedArrays(nums1, nums2) { + // ---- 入力検証(軽量&早期)---- + if (!Array.isArray(nums1) || !Array.isArray(nums2)) { + throw new TypeError("Both inputs must be arrays."); + } + const m = nums1.length | 0; + const n = nums2.length | 0; + + // 長さ制約 + if (m < 0 || m > 1000 || n < 0 || n > 1000) { + throw new RangeError("Array length out of allowed range [0,1000]."); + } + const total = m + n; + if (total < 1 || total > 2000) { + throw new RangeError("Total length must be in [1,2000]."); + } + + // 値域と数値単型、および非減少(昇順)検証 + validateArray(nums1); + validateArray(nums2); + if (!isNonDecreasing(nums1) || !isNonDecreasing(nums2)) { + throw new RangeError("Arrays must be sorted in non-decreasing order."); + } + + // ---- 本処理:二分探索パーティション ---- + // 常に nums1 を短い方にする + let A = nums1, + B = nums2; + let aLen = m, + bLen = n; + if (aLen > bLen) { + A = nums2; + B = nums1; + aLen = n; + bLen = m; + } + + const half = ((aLen + bLen + 1) / 2) | 0; // 左側の要素数 + let lo = 0, + hi = aLen; + + while (lo <= hi) { + const i = (lo + hi) >> 1; // A 側の切り取り数 + const j = half - i; // B 側の切り取り数 + + const aLeft = i === 0 ? -Infinity : A[i - 1]; + const aRight = i === aLen ? Infinity : A[i]; + const bLeft = j === 0 ? -Infinity : B[j - 1]; + const bRight = j === bLen ? Infinity : B[j]; + + // パーティション条件 + if (aLeft <= bRight && bLeft <= aRight) { + // 正しい分割 + if (((aLen + bLen) & 1) === 1) { + // 奇数:左側最大 + const leftMax = aLeft > bLeft ? aLeft : bLeft; + return leftMax; + } else { + // 偶数:左側最大と右側最小の平均 + const leftMax = aLeft > bLeft ? aLeft : bLeft; + const rightMin = aRight < bRight ? aRight : bRight; + return (leftMax + rightMin) / 2; + } + } else if (aLeft > bRight) { + // 左が重い → A の切り取りを減らす + hi = i - 1; + } else { + // 右が重い → A の切り取りを増やす + lo = i + 1; + } + } + + // ここに来ないはず(検証済み入力で必ず解がある) + throw new Error("Unexpected state: valid median not found."); +} + +/** + * 値域/型の軽量検証(数値・有限・値域内) + * @param {number[]} arr + * @throws {TypeError|RangeError} + */ +function validateArray(arr) { + const len = arr.length | 0; + for (let i = 0; i < len; i++) { + const v = arr[i]; + if (typeof v !== "number" || !Number.isFinite(v)) { + throw new TypeError("Arrays must contain finite numbers only."); + } + if (v < -1e6 || v > 1e6) { + throw new RangeError("Values must be within [-1e6, 1e6]."); + } + } +} + +/** + * 非減少(昇順)確認 + * @param {number[]} arr + * @returns {boolean} + */ +function isNonDecreasing(arr) { + const len = arr.length | 0; + for (let i = 1; i < len; i++) { + if (arr[i - 1] > arr[i]) return false; + } + return true; +} + +module.exports = { findMedianSortedArrays }; +// ``` + +// --- + +// ## 5. 追加メモ(JS最適化チェックリスト) + +// * ループは `for` を基本に(`map/forEach` 由来の割当回避)。 +// * 一時配列や `.sort()` を使わない。 +// * 例外はホットパス外(冒頭)で早期判定し、以降は分岐最小化。 +// * `±Infinity` を用いた境界処理で分岐の複雑化を抑制。 +// * 配列は数値単型を維持(`Number.isFinite` で検査)。 diff --git a/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.py b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.py new file mode 100644 index 0000000..464e253 --- /dev/null +++ b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.py @@ -0,0 +1,264 @@ +# 以下は “leetcodeでの回答フォーマット(Class 形式)” に準拠した提出用一式です。 +# 環境: **Python (CPython 3.11.10)**、外部ライブラリ不使用、**pylance で型エラーが出ない**よう型注釈を厳密化しています。 + +# --- + +# ## 1. 問題分析結果 + +# ### 競技プログラミング視点 + +# * 目標計算量は **O(log (m+n))**。短い配列を対象にした **二分探索パーティション法** が定石。 +# * 追加メモリ **O(1)**。配列のマージやソートは不要。 +# * エッジケース(片方空・極端な長さ差・重複・負数)でも同一ロジックで処理可能。 + +# ### 業務開発視点 + +# * **可読性/保守性**:`aLeft/aRight/bLeft/bRight` の境界値を `±inf` で一元化し分岐を簡潔化。 +# * **型安全性**:`List[int]` を明示、戻り値は `float`。ドキュメンテーション文字列で契約を明確化。 +# * **エラーハンドリング**:LeetCode では前提条件が保証されるため **例外送出は行わない**。ただし、補助的な内部検証関数を用意(本メソッドでは未使用)し、プロダクション移行時の堅牢化に備える。 + +# ### Python特有考慮 + +# * **CPython**:数値・配列アクセスは C 実装で高速。ホットパスは単純な `while` と添字アクセスに限定。 +# * **GIL**:CPU バウンドで並列化は無意味。本問題は単一スレッドで十分。 +# * **内蔵最適化**:`float('inf')` による境界処理、条件分岐の削減で分岐予測ミスを減らす。 + +# --- + +# ## 2. アルゴリズム比較表 + +# | アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 | +# | ---------------------- | ------------------: | -----: | ----------- | --- | --------- | ---------- | ----------- | +# | 方法A: 二分探索パーティション(採用) | **O(log min(m,n))** | O(1) | 中 | 中 | 不要 | 適 | 要件を満たす代表解 | +# | 方法B: 2 本ポインタで中央値まで線形走査 | O(m+n) | O(1) | 低 | 高 | 不要 | 適 | 簡潔だが計算量劣後 | +# | 方法C: 連結して `sorted()` | O((m+n) log(m+n)) | O(m+n) | 低 | 高 | `sorted` | 適 | メモリ・時間ともに不利 | + +# --- + +# ## 3. 採用アルゴリズムと根拠 + +# * **選択**:方法A(二分探索パーティション) +# * **理由**:要件の **O(log (m+n))** を満たし、追加メモリ O(1)。実装は短く、境界も `±inf` で素直に扱える。 +# * **Python最適化**: + +# * ループは `while`、添字アクセスのみ(クロージャやイテレータ生成を避ける)。 +# * 比較は単純な数値比較のみ。タプルや余計な一時オブジェクトを作らない。 + +# --- + +# ## 4. 検証(方針のみ・コード不要) + +# * **境界値**:`nums1=[]` / `nums2=[]`、要素が全て同値、極小/極大値(±1e6)、奇数/偶数合計長。 +# * **型**:`List[int]` のみ。戻り値 `float` は `int` も表現可能(2 → 2.0 互換)。 + +# --- + +# ## 5. 提出コード(LeetCode Class 形式) + +# ```python +# from __future__ import annotations +# from typing import List, Final + + +# class Solution: +# """ +# Median of Two Sorted Arrays +# - Time: O(log(min(m, n))) +# - Space: O(1) +# 実装は短い配列に対してのみ二分探索を行うパーティション法。 +# """ + +# # --- 競プロ用(LeetCode 実行エントリ) --- +# def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: +# """ +# Args: +# nums1: Non-decreasing sorted list of ints (length 0..1000) +# nums2: Non-decreasing sorted list of ints (length 0..1000) +# Returns: +# Median as float (even length -> average of two middles) + +# Note: +# LeetCode では前提条件が満たされるため、実行時例外は送出しない。 +# """ +# # 競技プログラミング最適化版を直接呼び出し +# return self._median_binary_partition(nums1, nums2) + +# # --- 競技プログラミング向け最適化実装 --- +# def _median_binary_partition(self, nums1: List[int], nums2: List[int]) -> float: +# # A を短い配列にする +# if len(nums1) > len(nums2): +# nums1, nums2 = nums2, nums1 + +# a: List[int] = nums1 +# b: List[int] = nums2 +# m: int = len(a) +# n: int = len(b) + +# # 片方が空でもロジックはそのまま動作 +# half: int = (m + n + 1) // 2 +# lo: int = 0 +# hi: int = m + +# INF: Final[float] = float("inf") + +# while lo <= hi: +# i: int = (lo + hi) // 2 # a側の左パート長 +# j: int = half - i # b側の左パート長 + +# a_left: float = -INF if i == 0 else float(a[i - 1]) +# a_right: float = INF if i == m else float(a[i]) +# b_left: float = -INF if j == 0 else float(b[j - 1]) +# b_right: float = INF if j == n else float(b[j]) + +# # 正しいパーティション条件 +# if a_left <= b_right and b_left <= a_right: +# if ((m + n) & 1) == 1: +# # 奇数: 左側最大が中央値 +# return a_left if a_left > b_left else b_left +# # 偶数: 左最大と右最小の平均 +# left_max: float = a_left if a_left > b_left else b_left +# right_min: float = a_right if a_right < b_right else b_right +# return (left_max + right_min) / 2.0 + +# # パーティションを調整 +# if a_left > b_right: +# hi = i - 1 # aの左が大きすぎる → a左を減らす +# else: +# lo = i + 1 # aの右が大きすぎる → a左を増やす + +# # 前提条件が満たされる限り到達しない +# # LeetCode 入力ではここに来ない +# return 0.0 + +# # --- 業務開発向け:堅牢化(本提出では未使用だが参考実装) --- +# def _validate_non_decreasing(self, arr: List[int]) -> bool: +# """非減少(昇順)確認。pylance対応の厳密 int 配列のみを前提。""" +# # for ループ + 直接添字アクセス(最速の部類) +# for i in range(1, len(arr)): +# if arr[i - 1] > arr[i]: +# return False +# return True +# ``` + +# ### 実装ポイント + +# * **while + 添字アクセス**のみでホットパスを構成(イテレータ・クロージャ不要)。 +# * **`float('inf')`** による境界処理で `if` 分岐を単純化。 +# * 返り値は `float` で一貫。奇数長でも `float` を返す(LeetCode 互換)。 + +# **同じ(二分探索パーティション)アルゴリズム**のまま、CPython 3.11 での実行速度・メモリを意識してホットパスを削った改良版を示します。 + +# --- + +# ## 1) どこを削るか(短評) + +# * **不要な `float()` キャスト除去**:ループ内での `float(a[i])` 等をやめ、**すべて `int`** で比較。偶数長の **最後だけ** `0.5` 乗算で `float` 化。 +# * **分岐回数/関数呼び出し削減**:`max/min` を使わず **条件演算子で直接比較**、タプル生成を回避。 +# * **ローカル変数への束縛**:`nums1/nums2/len` をローカルに束縛(属性/グローバル参照の回数を低減)。 +# * **整数のセンチネル使用**:境界は `±INF` の代わりに **値域を超える固定整数**(例: `NEG = -10_000_007`, `POS = 10_000_007`)を使用し、**int↔float 混在比較を回避**。 +# * **再計算しない**:`total_is_odd` を事前計算、`half` は固定。 +# * **オブジェクト生成ゼロ**:一時タプル/リスト/クロージャを作らない。 + +# > 制約が ±1e6 なので、センチネルは ±1e7 で十分安全です。 + +# --- + +# ## 2) 改良版コード(LeetCode “Class 形式”・pylance対応) + +# ```python +from __future__ import annotations + +from typing import Final, List + + +class Solution: + """ + Median of Two Sorted Arrays + - Time: O(log(min(m, n))) + - Space: O(1) + 速度・メモリ最適化版(同一アルゴリズム、ホットパス最小化) + """ + + def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: + # --- 短い配列を A にする(参照束縛で属性参照も削減) --- + if len(nums1) > len(nums2): + nums1, nums2 = nums2, nums1 + + A: List[int] = nums1 + B: List[int] = nums2 + a_len: int = len(A) + b_len: int = len(B) + + # 総数・奇偶は事前計算してループ内で条件分岐を減らす + total: int = a_len + b_len + total_is_odd: bool = (total & 1) == 1 + half: int = (total + 1) >> 1 + + # 整数センチネル(制約±1e6 を大きく超える) + NEG: Final[int] = -10_000_007 + POS: Final[int] = +10_000_007 + + lo: int = 0 + hi: int = a_len + + # --- 中央探索(ホットパス) --- + # すべて int 比較。最後にのみ float を作る。 + while lo <= hi: + i: int = (lo + hi) >> 1 # A の左パート長 + j: int = half - i # B の左パート長 + + a_left: int = NEG if i == 0 else A[i - 1] + a_right: int = POS if i == a_len else A[i] + b_left: int = NEG if j == 0 else B[j - 1] + b_right: int = POS if j == b_len else B[j] + + # 正しいパーティション条件 + if a_left <= b_right and b_left <= a_right: + if total_is_odd: + # 奇数:左側最大(float で返す) + return float(a_left if a_left > b_left else b_left) + # 偶数:左最大・右最小の平均(0.5 乗算で float 化) + left_max: int = a_left if a_left > b_left else b_left + right_min: int = a_right if a_right < b_right else b_right + return (left_max + right_min) * 0.5 + + # パーティション調整(分岐は2択のみ) + if a_left > b_right: + hi = i - 1 + else: + lo = i + 1 + + # 前提条件上ここには来ない + return 0.0 + + +# ``` + +# ### 変更点が効く理由 + +# * **int 比較のみ**:`int` と `float('inf')` の比較は内部で float に寄せるため、**キャスト/型変換コスト**が乗ります。センチネルを `int` にするとこのコストを避けられます。 +# * **演算子比較 > max/min**:`max(x, y)` は関数呼び出し+タプル類似の引数処理になるため、`x if x > y else y` の方がホットパスでは速いことが多いです。 +# * **ループ内割当の削減**:`total_is_odd` などを外に逃がして **条件式の評価回数**を抑制。 + +# --- + +# ## 3) 期待できる効果(目安) + +# * **Runtime**:`float()` キャスト除去・整数センチネルにより分岐/型変換が減り、**数 ms 改善**が見込めます。 +# * **Memory**:もともと O(1) ですが、一時オブジェクト(タプル/float)の生成を減らすため、**微減**します。 + +# > もちろんジャッジ環境のノイズはありますが、上のような **ホットパス縮小** は安定して効く最適化です。 + +# --- + +# ## 4) 追加の微調整オプション(任意) + +# * **`(lo + hi) >> 1` の継続**:`// 2` より ほんの僅かに速いケースが多い(CPython 実装依存)。 +# * **早期 return の単純化**:`total_is_odd` を使い、奇数/偶数で分岐を 1 回だけに。 +# * **関数分割の抑制**:ホットパス中のヘルパー呼び出しを完全排除(既に実施済み)。 + +# --- + +# ### まとめ + +# アルゴリズムは不変のまま、**型変換・関数呼び出し・分岐**を削ることで **実行時間・メモリ**ともに改善できます。上記の改良版は LeetCode の要件(Class 形式 / 返り値 float)を満たしつつ、pylance でも型エラーにならない実装です。 diff --git a/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.ts b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.ts new file mode 100644 index 0000000..7b0d0f7 --- /dev/null +++ b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/Median_of_Two_Sorted_Arrays.ts @@ -0,0 +1,201 @@ +// 以下は “leetcodeでの回答フォーマット” を TypeScript(Node.js v22.14.0 / **ESM** / 外部ライブラリ不可)で満たす提出用一式です。 +// ※関数は **Pure**/副作用なし、strict 前提の型安全実装です。 + +// --- + +// ## 1. 問題の分析 + +// ### 競技プログラミング視点での分析 + +// * 目標計算量は **O(log (m+n))**。典型解は **二分探索パーティション法**:短い方の配列のみを二分探索し、左右の最大/最小の関係から中央値を決定。 +// * 追加メモリ **O(1)**。マージ配列を作らないためメモリ効率が高い。 +// * 空配列/サイズ非対称/重複値/負数を含む境界でもロジックは一貫。 + +// **メモリ最小化方針** + +// * 破壊的更新なし(Pure)。一時配列や `sort` は使用しない。`±Infinity` による境界処理で条件分岐を簡素化。 + +// ### 業務開発視点での分析 + +// * **型安全**:引数は `readonly number[]`、実行時も数値・有限性・値域・非減少(昇順)を検証し、違反は `TypeError` / `RangeError`。 +// * **可読性/保守性**:`aLeft/aRight/bLeft/bRight` の命名で意図を明確化。入力検証をホットパス外で早期に実行。 +// * **例外戦略**:仕様違反を明確に区別(型 → `TypeError`、制約 → `RangeError`、到達不能な異常 → `Error`)。 + +// ### TypeScript特有の考慮点 + +// * **型推論**:インデックスは number、配列は `readonly number[]` で不変性を明示。 +// * **コンパイル時最適化**:不要なジェネリクスを避け、単純な数値演算に限定して JIT 最適化を阻害しない。 +// * **null 安全**:`Infinity` ガードで未定義分岐を消し、`if` ネストを抑制。 + +// --- + +// ## 2. アルゴリズムアプローチ比較 + +// | アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 | +// | --------------------------- | ------------------: | -----: | ------: | ---: | --- | ------------- | +// | 方法A: 二分探索パーティション(採用) | **O(log min(m,n))** | O(1) | 中 | 高 | 中 | 目標計算量を満たす標準解 | +// | 方法B: 2本のポインタで中央値まで線形走査 | O(m+n) | O(1) | 低 | 高 | 高 | 実装は簡単だが計算量で不利 | +// | 方法C: 連結して `.sort()` 後に中央値取得 | O((m+n) log(m+n)) | O(m+n) | 低 | 高 | 高 | 追加メモリ/ソートコスト大 | + +// --- + +// ## 3. 選択したアルゴリズムと理由 + +// * **選択したアプローチ**: 方法A(短い配列に対する二分探索パーティション) +// * **理由**: + +// * 要件の **O(log (m+n))** を満たしつつ **O(1)** 追加メモリ。 +// * TS での境界処理が明確・安全(`±Infinity` と `readonly` で分岐・副作用を最小化)。 +// * 実装が比較的コンパクトで、検証容易。 +// * **TypeScript特有の最適化ポイント**: + +// * `readonly number[]` で不変性を静的保証。 +// * 早期入力検証でホットパスの分岐を削減。 +// * 数値単型・単純な `while` ループで V8 の最適化に寄与。 + +// --- + +// ## 4. 実装コード(**ESM / strict mode / Pure**) + +// ```typescript +// solution.ts +/* eslint-disable no-plusplus */ +/* Node.js v22.14.0 / ESM / no external libs + tsconfig 推奨: "strict": true, "module": "ESNext", "target": "ES2022" + 実行は LeetCode の TypeScript 環境前提(ローカル実行時は tsc で JS に変換) +*/ + +/** + * Median of Two Sorted Arrays (LeetCode) + * 二分探索パーティション O(log(min(m,n))) / 追加メモリ O(1) + * + * @param nums1 - 昇順ソート済み配列(長さ 0..1000, 値域 [-1e6, 1e6]) + * @param nums2 - 昇順ソート済み配列(長さ 0..1000, 値域 [-1e6, 1e6]) + * @returns 中央値(偶数個のときは2値平均) + * @throws {TypeError} 引数が配列でない / 数値でない + * @throws {RangeError} 長さ・値域・非昇順の制約違反 + * @complexity Time: O(log(min(m,n))), Space: O(1) + */ +export function findMedianSortedArrays( + nums1: readonly number[], + nums2: readonly number[] +): number { + // ---- 入力検証(ホットパス外で早期)---- + if (!Array.isArray(nums1) || !Array.isArray(nums2)) { + throw new TypeError("Both inputs must be arrays."); + } + const m = nums1.length; + const n = nums2.length; + + if (m < 0 || m > 1000 || n < 0 || n > 1000) { + throw new RangeError("Array length out of allowed range [0, 1000]."); + } + const total = m + n; + if (total < 1 || total > 2000) { + throw new RangeError("Total length must be in [1, 2000]."); + } + + validateNumberArray(nums1); + validateNumberArray(nums2); + if (!isNonDecreasing(nums1) || !isNonDecreasing(nums2)) { + throw new RangeError("Arrays must be sorted in non-decreasing order."); + } + + // ---- 本処理:短い配列に対して二分探索 ---- + let A = nums1, + B = nums2; + let aLen = m, + bLen = n; + if (aLen > bLen) { + A = nums2; + B = nums1; + aLen = n; + bLen = m; + } + + const half = Math.floor((aLen + bLen + 1) / 2); + let lo = 0, + hi = aLen; + + while (lo <= hi) { + const i = (lo + hi) >>> 1; // A 側の切り取り + const j = half - i; // B 側の切り取り + + const aLeft = i === 0 ? -Infinity : A[i - 1]!; + const aRight = i === aLen ? Infinity : A[i]!; + const bLeft = j === 0 ? -Infinity : B[j - 1]!; + const bRight = j === bLen ? Infinity : B[j]!; + + if (aLeft <= bRight && bLeft <= aRight) { + // 正しいパーティション + if (((aLen + bLen) & 1) === 1) { + // 奇数:左側最大が中央値 + return aLeft > bLeft ? aLeft : bLeft; + } else { + // 偶数:左最大と右最小の平均 + const leftMax = aLeft > bLeft ? aLeft : bLeft; + const rightMin = aRight < bRight ? aRight : bRight; + return (leftMax + rightMin) / 2; + } + } else if (aLeft > bRight) { + // 左が重い → i を小さく + hi = i - 1; + } else { + // 右が重い → i を大きく + lo = i + 1; + } + } + + // ここには来ない(検証済み入力で中央値は必ず存在) + throw new Error("Unexpected state: valid median not found."); +} + +/** 数値配列(有限・値域)検証 */ +function validateNumberArray(arr: readonly number[]): void { + const len = arr.length; + for (let i = 0; i < len; i++) { + const v = arr[i]; + if (typeof v !== "number" || !Number.isFinite(v)) { + throw new TypeError("Arrays must contain finite numbers only."); + } + if (v < -1e6 || v > 1e6) { + throw new RangeError("Values must be within [-1e6, 1e6]."); + } + } +} + +/** 非減少(昇順)確認 */ +function isNonDecreasing(arr: readonly number[]): boolean { + const len = arr.length; + for (let i = 1; i < len; i++) { + if (arr[i - 1]! > arr[i]!) return false; + } + return true; +} +// ``` + +// > **LeetCode 提出時の関数名/シグネチャ**はそのまま `findMedianSortedArrays(nums1: number[], nums2: number[]): number` で互換です。 +// > ローカルでの ESM 実行は `tsc` で JS にビルドしてから `node` 実行を想定(外部ツール導入の指示は行いません)。 + +// --- + +// ## TypeScript 固有の最適化観点 + +// * **型安全性** + +// * `readonly number[]` で不変性を表明し、副作用をコンパイル時に抑止。 +// * 実行時も `TypeError`/`RangeError` を明確化して失敗モードを限定。 +// * **推論/簡潔性** + +// * 余計なジェネリクスは使わず、数値単型に限定して JIT 最適化を阻害しない。 +// * **null 安全性** + +// * 配列境界は `±Infinity` を使用し未定義アクセスを回避、`!` は境界での存在保証の意図を明確化。 + +// --- + +// ### まとめ + +// * 目標計算量 **O(log (m+n))** を満たす標準解(短い配列に対する二分探索パーティション)。 +// * **Pure / ESM / strict / 外部ライブラリ不使用** を遵守。 +// * 型安全な入力検証 + シンプルなホットパスで、速度・可読性・保守性のバランスを最適化。 diff --git a/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/README.md b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/README.md new file mode 100644 index 0000000..926eb3f --- /dev/null +++ b/Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/GPT/README.md @@ -0,0 +1,574 @@ +# Median of Two Sorted Arrays - 二分探索による中央値計算 + +## Table of Contents + +- [概要](#overview) +- [アルゴリズム要点(TL;DR)](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python 実装](#impl) +- [CPython 最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +

概要

+ +- **問題**: 2 つのソート済み配列 `nums1`, `nums2` が与えられる。両者を統合した配列の中央値を求めよ。 +- **制約**: + + - 配列要素は昇順にソート済み。 + - 配列の長さは 0 以上。 + - 時間計算量は **O(log(min(m, n)))** を要求。 + +- **要件**: + + - 正しい中央値を返す。 + - 空配列が含まれても対応。 + - 奇数長・偶数長の両方に対応。 + +--- + +

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

+ +- 戦略: **二分探索パーティション法** +- 手順: + + - 短い配列を対象に二分探索。 + - 配列を左右に分割し、左側最大値と右側最小値で中央値を決定。 + - 境界条件は **センチネル値(±∞ の代替 int)** を利用。 + +- データ構造: 配列のみ(外部構造不要) +- 計算量: + + - Time: **O(log(min(m, n)))** + - Space: **O(1)** + +- 最適化: + + - `int` センチネルで `float` キャストを削減。 + - `max/min` を避け、条件演算子で高速化。 + - ローカル変数束縛で属性参照を削減。 + +--- + +

図解

+ +```mermaid +flowchart TD + Start[Start binary search] --> Partition[Choose partition i] + Partition --> Compute[Compute j = half - i] + Compute --> Check{a_left ≤ b_right AND b_left ≤ a_right} + Check -- Yes --> Median[Compute median from left_max and right_min] + Check -- No --> Adjust{a_left > b_right ?} + Adjust -- Yes --> MoveLeft[hi = i - 1] + Adjust -- No --> MoveRight[lo = i + 1] + MoveLeft --> Partition + MoveRight --> Partition + Median --> End[Return result] +``` + +**説明**: + +- 配列 A と B をパーティション分割し、境界値を比較する。 +- 条件が揃えば中央値を算出、揃わなければ探索範囲を調整する。 + +--- + +```mermaid +graph LR + subgraph Precheck + A[Input arrays] --> B[Ensure A shorter] + B --> C[Calculate total length] + end + subgraph Core + C --> D[Binary search on A] + D --> E[Partition A and B] + E --> F[Check partition validity] + F --> G[Compute median] + end + G --> H[Output] +``` + +**説明**: +入力の正規化(短い方を選択)から始まり、二分探索により中央値を導出するデータフローを示す。 + +--- + +

正しさのスケッチ

+ +- **不変条件**: 各反復で `i + j = half` を維持。 +- **基底条件**: `i` が範囲 `[0, len(A)]` を越えない。 +- **網羅性**: 境界条件に `±INF`(安全な int センチネル)を使用することで、両端のケースを含め全ての分割を網羅。 +- **終了性**: 二分探索により探索範囲が縮小し、必ず終了する。 + +--- + +

計算量

+ +- **時間計算量**: O(log(min(m, n))) + + - 二分探索のため。 + +- **空間計算量**: O(1) + + - 補助配列を作成せず定数メモリで動作。 + +比較表: + +| 手法 | 時間 | 空間 | 備考 | +| ---------------------- | ----------------- | ------ | ------------ | +| 全マージ | O(m+n) | O(m+n) | 遅い | +| ソート結合 | O((m+n) log(m+n)) | O(m+n) | 不要なソート | +| 二分探索パーティション | O(log(min(m,n))) | O(1) | 最適 | + +--- + +

Python 実装

+ +```python +from __future__ import annotations +from typing import List, Final + + +class Solution: + """ + Median of Two Sorted Arrays + - Time: O(log(min(m, n))) + - Space: O(1) + """ + + def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float: + # 短い配列を A に設定 + if len(nums1) > len(nums2): + nums1, nums2 = nums2, nums1 + + A: List[int] = nums1 + B: List[int] = nums2 + a_len: int = len(A) + b_len: int = len(B) + + total: int = a_len + b_len + total_is_odd: bool = (total & 1) == 1 + half: int = (total + 1) >> 1 + + # センチネル(制約±1e6より大きい) + NEG: Final[int] = -10_000_007 + POS: Final[int] = +10_000_007 + + lo: int = 0 + hi: int = a_len + + while lo <= hi: + i: int = (lo + hi) >> 1 + j: int = half - i + + a_left: int = NEG if i == 0 else A[i - 1] + a_right: int = POS if i == a_len else A[i] + b_left: int = NEG if j == 0 else B[j - 1] + b_right: int = POS if j == b_len else B[j] + + if a_left <= b_right and b_left <= a_right: + if total_is_odd: + return float(a_left if a_left > b_left else b_left) + left_max: int = a_left if a_left > b_left else b_left + right_min: int = a_right if a_right < b_right else b_right + return (left_max + right_min) * 0.5 + + if a_left > b_right: + hi = i - 1 + else: + lo = i + 1 + + return 0.0 +``` + +--- + +

CPython最適化ポイント

+ +- **int センチネル利用**: `float('inf')` より速い。 +- **条件演算子**: `max/min` 関数呼び出しを避ける。 +- **ローカル変数束縛**: `len()` や属性参照を外に出す。 +- **ビットシフト**: `(lo + hi) >> 1` により整数除算を高速化。 + +--- + +

エッジケースと検証観点

+ +- 両配列が空 → 問題設定上非定義だが、実装は 0.0 を返す。 +- 片方が空 → 中央値はもう一方の中央値。 +- 配列の長さが 1 vs 大きい数 → 正しく動作。 +- 負数/大数(±1e6 近辺) → センチネル ±1e7 で安全。 +- 偶数長と奇数長の両方で正しい中央値を返す。 + +--- + +

FAQ

+ +- **Q: なぜ O(log(min(m,n))) が必要?** + A: 全マージやソートは遅すぎるため、短い配列に対して二分探索を行う必要がある。 + +- **Q: `float('inf')` を使わない理由は?** + A: 型変換コスト削減と一貫した `int` 比較のため。 + +- **Q: 線形マージ解法と比べた利点は?** + A: 入力が大きい場合に劇的に速く、かつ O(1) 空間で済む。 + +--- + +✅ この README は **学習者向けの解説 + 実務向け最適化** の両立を意図して構成されています。 + +了解です。ご指定のコード断片の **ループ 1 回あたりで何を比較して、なぜ `lo` か `hi` を動かすのか** を、パース安全な Mermaid と ASCII 図で具体的に示します。 +(Mermaid は英語ラベルのみ、`-->`、`≤` エスケープ、1 行 1 エッジのルールを厳守) + +--- + +## 1) ループ内ロジックの流れ図 + +```mermaid +flowchart TD + Start[Start iteration] --> PickI[Compute i as mid] + PickI --> PickJ[Compute j as half minus i] + PickJ --> ReadA[Read a_left a_right] + ReadA --> ReadB[Read b_left b_right] + ReadB --> Check{a_left ≤ b_right AND b_left ≤ a_right} + Check -- Yes --> OddCheck{Total length is odd} + OddCheck -- Yes --> RetOdd[Return max a_left b_left] + OddCheck -- No --> RetEven[Return avg of left_max and right_min] + Check -- No --> TooLeft{a_left > b_right} + TooLeft -- Yes --> MoveHi[hi = i - 1] + TooLeft -- No --> MoveLo[lo = i + 1] + MoveHi --> NextIter[Next iteration] + MoveLo --> NextIter + RetOdd --> End[End] + RetEven --> End + NextIter --> PickI +``` + +### **要点 1(日本語)** + +- `i` を二分探索の中央に取り、対応する `j = half - i` を決めます。 +- `a_left, a_right, b_left, b_right` は **i と j の両側の境界値**。 +- 条件 `a_left ≤ b_right` かつ `b_left ≤ a_right` が**両方満たされれば**「正しいパーティション」到達。 + + - 総長が奇数なら **左側最大**が中央値。 + - 偶数なら **左側最大と右側最小の平均**が中央値。 + +- 条件が崩れている場合: + + - `a_left > b_right` なら **A の左が詰まり過ぎ** → `hi = i - 1`(i を小さく)。 + - それ以外は **A の右が大き過ぎ** → `lo = i + 1`(i を大きく)。 + +--- + +## 2) パーティションの可視化(概念図) + +```mermaid +graph LR + subgraph Arrays + A0[A left part] --> A1[A right part] + B0[B left part] --> B1[B right part] + end + subgraph Boundaries + Lmax[Left max is max a_left b_left] + Rmin[Right min is min a_right b_right] + end + A0 --> Lmax + B0 --> Lmax + A1 --> Rmin + B1 --> Rmin +``` + +### **要点 2(日本語)** + +- A と B を「左パート」と「右パート」に切ると、**左側最大**と**右側最小**だけで中央値が決まります。 +- 「正しいパーティション」とは **左側の全要素 ≤ 右側の全要素** を満たす分割です。 + それを境界 4 値 `a_left, a_right, b_left, b_right` の不等式でチェックしています。 + +--- + +## 3) 具体例ウォークスルー + +**例**: +A = [1, 3, 8], B = [2, 7, 10, 12] +合計長 = 7 → `half = 4`、奇数長 → 最終的に**左側最大**が中央値。 + +### 1 回目の反復 + +- 初期 `lo = 0, hi = len(A) = 3` +- `i = (0 + 3) >> 1 = 1` +- `j = half - i = 4 - 1 = 3` +- 境界値 + + - `a_left = A[i-1] = A[0] = 1` + - `a_right = A[i] = A[1] = 3` + - `b_left = B[j-1] = B[2] = 10` + - `b_right = B[j] = B[3] = 12` + +- 判定 + + - `a_left ≤ b_right` → `1 ≤ 12` は真 + - `b_left ≤ a_right` → `10 ≤ 3` は **偽** + +- 結論 + `b_left > a_right`(= A の右側が小さすぎる)→ **i を増やす**:`lo = i + 1 = 2` + +### 2 回目の反復 + +- `lo = 2, hi = 3` +- `i = (2 + 3) >> 1 = 2` +- `j = 4 - 2 = 2` +- 境界値 + + - `a_left = A[1] = 3` + - `a_right = A[2] = 8` + - `b_left = B[1] = 7` + - `b_right = B[2] = 10` + +- 判定 + + - `a_left ≤ b_right` → `3 ≤ 10` 真 + - `b_left ≤ a_right` → `7 ≤ 8` 真 + +- **正しいパーティション** 到達 + + - 奇数長 → 中央値は **max(a_left, b_left) = max(3, 7) = 7** + +### ASCII 断面図 + +```text +A: [ 1 | 3 | 8 ] + ^ + i=2 => a_left=3, a_right=8 + +B: [ 2 | 7 | 10 | 12 ] + ^ + j=2 => b_left=7, b_right=10 + +Left side elements = [1,3] + [2,7] -> left_max = 7 +Right side elements = [8] + [10,12] -> right_min = 8 or 10 +Odd total -> median = left_max = 7 +``` + +--- + +## 4) センチネル値の役割(NEG, POS) + +**なぜ使うか?** + +- i や j が 0 または最終位置にあるとき、`A[i-1]` や `B[j]` は **存在しない** 可能性がある。 +- その際に + + - 左境界の無い側は **極小**(NEG)、 + - 右境界の無い側は **極大**(POS) + を代入すると、**「比較式だけで」端を処理できる**。 + +### **効果** + +- `if i == 0` のような個別分岐を減らし、`a_left ≤ b_right AND b_left ≤ a_right` の **1 本の条件**で判定可能。 +- 実装が簡潔になり、分岐ミス(バグ)も減る。 + +--- + +## 5) `hi` と `lo` の調整が意味すること + +- `a_left > b_right` + → **A の左側に大き過ぎる値が混ざっている** + → 左側を減らすため **i を小さく**(`hi = i - 1`) +- それ以外(このとき `b_left > a_right` が成り立つ) + → **A の右側が小さ過ぎる**(右側にもっと大きい値を回したい) + → 左側を増やすため **i を大きく**(`lo = i + 1`) + +これにより、`i + j = half` を不変に保ちながら、**「左側最大 ≤ 右側最小」** に近づくようにパーティションを微調整していきます。 + +--- + +### まとめ + +- ループの本質は **「A の切り口 i を二分探索で動かし、左側最大と右側最小が交差しない位置を見つける」** こと。 +- 条件が整えば **奇数長は左最大、偶数長は左最大と右最小の平均** で即座に答えが出ます。 +- センチネルと 4 境界だけ見ればよい設計により、**分岐を最小化**しつつ **正しさ** と **実装簡潔性** を両立しています。 + +いい質問です 👍 +ここでは **TypeScript の `>>>`** と、**JavaScript/Python の `>>`** について、それぞれの意味と使い方を具体的に整理して説明します。 + +--- + +## 1. TypeScript の `>>>` 演算子 + +### 意味 1 + +- `>>>` は **符号なし右シフト (zero-fill right shift)** 演算子です。 +- 左オペランドを右オペランドのビット数だけ右にシフトし、空いた左側のビットには常に **0** が埋められます。 +- 結果は常に **符号なし 32 ビット整数 (0 以上)** になります。 + +### 例 1 + +```typescript +// TypeScript (Node.js v18+) +let x: number = -8; + +console.log(x >> 2); // -2 (算術右シフト、符号を保持) +console.log(x >>> 2); // 1073741822 (符号なし右シフト、ゼロ埋め) +``` + +- `-8` の 32 ビット表現: `11111111 11111111 11111111 11111000` +- `x >> 2`: 符号ビットを保持 → `11111111 11111111 11111111 11111110` → `-2` +- `x >>> 2`: 左をゼロで埋める → `00111111 11111111 11111111 11111110` → `1073741822` + +--- + +## 2. JavaScript の `>>` 演算子 + +### 意味 2 + +- `>>` は **算術右シフト (arithmetic right shift)** 演算子です。 +- 左オペランドを右にシフトし、**符号ビットを保持**(負数なら 1、正数なら 0 が左側に埋められる)。 + +### 例 2 + +```javascript +let a = 16; +let b = -16; + +console.log(a >> 2); // 4 (16 / 4) +console.log(b >> 2); // -4 (-16 / 4) +``` + +--- + +## 3. Python の `>>` 演算子 + +### 意味 3 + +- Python でも `>>` は **算術右シフト** 演算子です。 +- JavaScript と同じく符号ビットを保持します。 +- Python の整数は「任意精度整数」なので、桁あふれは起こりません。 + +### 例 3 + +```python +a = 16 +b = -16 + +print(a >> 2) # 4 +print(b >> 2) # -4 +``` + +--- + +## 4. まとめ表 + +| 言語 | 演算子 | 名称 | 左ビットの埋め方 | 例 (`-8 >> 2` / `-8 >>> 2`) | +| -------------- | ------ | --------------------------- | ---------------- | --------------------------- | +| **TypeScript** | `>>` | 算術右シフト | 符号ビット保持 | `-8 >> 2 = -2` | +| | `>>>` | 符号なし右シフト (ゼロ埋め) | 常に `0` | `-8 >>> 2 = 1073741822` | +| **JavaScript** | `>>` | 算術右シフト | 符号ビット保持 | `-8 >> 2 = -2` | +| | `>>>` | 符号なし右シフト | 常に `0` | `-8 >>> 2 = 1073741822` | +| **Python** | `>>` | 算術右シフトのみ | 符号ビット保持 | `-8 >> 2 = -2` | +| | `>>>` | **存在しない** | ― | ― | + +--- + +✅ **要点** + +- `>>>` は **JS/TS 限定**(Python にはない)。 +- JS/TS の `>>>` は **負数を大きな正数に変換するケース**があるので注意。 +- Python の整数は任意精度なので `>>>` は不要(存在しない)。 + +--- + +## **ビット列の図解 (32bit バイナリ)** + +TypeScript / JavaScript の `>>`(算術右シフト)と `>>>`(符号なし右シフト)、 +Python の `>>`(算術右シフトのみ)の違いを **32 ビットバイナリ図解** で整理します。 + +--- + +### 1. 基本の例題 + +例として **`x = -8` を右に 2 ビットシフト**するケースを扱います。 + +--- + +### 2. -8 の 32 ビット表現 + +```text +-8 の 32bit 符号付き整数表現 (2の補数表現): +11111111 11111111 11111111 11111000 +``` + +- 先頭ビット = `1` → 負数 +- 末尾の `1000` が `8` を示す + +--- + +### 3. 算術右シフト (`>>`) の場合 + +算術右シフトは **符号ビットを保持** します。 + +```text +-8 >> 2 +11111111 11111111 11111111 11111110 ← 左を「1」で埋める += -2 +``` + +👉 符号が残るので **負の数のまま**。 + +--- + +### 4. 符号なし右シフト (`>>>`) の場合 + +符号なし右シフトは **左を常に 0 で埋める** ため、負数が大きな正数に変換されます。 + +```text +-8 >>> 2 +00111111 11111111 11111111 11111110 ← 左を「0」で埋める += 1073741822 +``` + +👉 負数が **巨大な正数** になる。 + +--- + +### 5. Python の場合 + +Python には `>>>` が存在せず、`>>` は **算術右シフトのみ**です。 + +```python +-8 >> 2 # -2 +``` + +Python の整数は任意精度なので、32 ビットに限定されません。 +そのため「0 埋めシフト」の `>>>` が不要で実装されていません。 + +--- + +### 6. 図解まとめ(Mermaid フローチャート風) + +```mermaid +flowchart TD + A["-8 (32bit: 11111111 11111111 11111111 11111000)"] + A -->|">> 2 (算術右シフト)"| B["11111111 11111111 11111111 11111110 = -2"] + A -->|">>> 2 (符号なし右シフト)"| C["00111111 11111111 11111111 11111110 = 1073741822"] + A -->|">> 2 (Python)"| D["11111111 11111111 11111111 11111110 = -2"] +``` + +--- + +### 7. まとめ表 + +| 言語 / 演算子 | 左ビットの埋め方 | `-8 >> 2` | `-8 >>> 2` | +| -------------------- | ---------------- | --------- | --------------- | +| **TypeScript** `>>` | 符号ビット保持 | -2 | ― | +| **TypeScript** `>>>` | 常に 0 埋め | ― | 1073741822 | +| **JavaScript** `>>` | 符号ビット保持 | -2 | ― | +| **JavaScript** `>>>` | 常に 0 埋め | ― | 1073741822 | +| **Python** `>>` | 符号ビット保持 | -2 | ―(存在しない) | + +--- + +✅ これで「`>>>` は JS/TS 限定のゼロ埋め右シフト」「Python は算術右シフトのみ」という違いが、ビット列でハッキリ見えると思います。 diff --git a/README.md b/README.md index 30a9677..b017614 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,22 @@ [![GitHub Stars](https://img.shields.io/github/stars/myoshi2891/Algorithm-DataStructures-Math-SQL?style=flat-square)](https://github.com/myoshi2891/Algorithm-DataStructures-Math-SQL/stargazers) [![GitHub Forks](https://img.shields.io/github/forks/myoshi2891/Algorithm-DataStructures-Math-SQL?style=flat-square)](https://github.com/myoshi2891/Algorithm-DataStructures-Math-SQL/network/members) -[![Languages](https://img.shields.io/badge/Languages-Python%20|%20TypeScript%20|%20JavaScript-blue?style=flat-square)](#) +[![Languages](https://img.shields.io/badge/Languages-Python%20|%20TypeScript%20|%20JavaScript-blue?style=flat-square)] [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/myoshi2891/Algorithm-DataStructures-Math-SQL) ## 🔍 概要(Overview) -本ドキュメントは **Algorithm-DataStructures** リポジトリの包括的な概要を示します。 -このリポジトリは、**アルゴリズム実装・インタラクティブ可視化・性能ベンチマーク**を統合した多言語教育プラットフォームです。 +本ドキュメントは **Algorithm-DataStructures** リポジトリの包括的な概要を示します。 +このリポジトリは、**アルゴリズム実装・インタラクティブ可視化・性能ベンチマーク**を統合した多言語教育プラットフォームです。 競技プログラミング、技術面接、コンピュータサイエンス教育向けの **最適化されたリファレンス実装集** として機能します。 --- ## 🎯 目的と範囲(Purpose and Scope) -- 主要アルゴリズムカテゴリの包括的実装 -- 各言語(Python / TypeScript / JavaScript)における統一的ロジック -- 教育・可視化・パフォーマンス評価の統合フレームワーク +- 主要アルゴリズムカテゴリの包括的実装 +- 各言語(Python / TypeScript / JavaScript)における統一的ロジック +- 教育・可視化・パフォーマンス評価の統合フレームワーク - 学習・面接・競技環境のすべてに対応 --- @@ -37,7 +37,7 @@ flowchart TD B3 -->|「構造実装」| C3["Tree / Graph / BIT / Stack"] B4 -->|「動的可視化」| C4["HTML / Canvas / JS Interactives"] B5 -->|「性能分析」| C5["Performance.now() ・ Timing Tools"] -```` +``` --- @@ -45,11 +45,11 @@ flowchart TD 各アルゴリズムは **共通ロジック** を維持しながら、各言語の特性を活かして最適化されています。 -| 言語 | 主な構成 | 型システム | 最適化手法 | -| -------------- | --------------------- | -------------------- | --------------------------- | -| **Python** | `class Solution` | `typing.List` / 型ヒント | `ord() - 48` / リスト内包表記 | -| **TypeScript** | `function multiply()` | 厳格な型定義 (`number[]`) | コンパイル時型安全性 | -| **JavaScript** | `var multiply` | 動的型付け | `charCodeAt() - 48` / V8最適化 | +| 言語 | 主な構成 | 型システム | 最適化手法 | +| -------------- | --------------------- | ------------------------- | ------------------------------- | +| **Python** | `class Solution` | `typing.List` / 型ヒント | `ord() - 48` / リスト内包表記 | +| **TypeScript** | `function multiply()` | 厳格な型定義 (`number[]`) | コンパイル時型安全性 | +| **JavaScript** | `var multiply` | 動的型付け | `charCodeAt() - 48` / V8 最適化 | ```mermaid flowchart LR @@ -64,13 +64,13 @@ flowchart LR リポジトリは、**計算量と応用領域** に基づいてアルゴリズムを体系化しています。 -| カテゴリ | 代表関数 | 時間計算量 | 空間計算量 | 対応言語 | -| ----------------------- | ---------------------------------------- | ---------- | -------- | -------------- | -| **Binary Search** | `search()` / `findMedianSortedArrays()` | O(log n) | O(1) | Python, TS, JS | -| **Mathematical** | `Solution.multiply()` / `multiply()` | O(m×n) | O(m+n) | Python, TS, JS | -| **Dynamic Programming** | `countWays()` / `count_ways()` | O(n)〜O(n²) | O(n) | Python, TS, JS | -| **Backtracking** | `combinationSum()` / `backtrack()` | O(2^n) | O(log n) | TS, JS | -| **Data Structures** | `BinaryIndexedTree.update()` / `query()` | O(log n) | O(n) | Python | +| カテゴリ | 代表関数 | 時間計算量 | 空間計算量 | 対応言語 | +| ----------------------- | ---------------------------------------- | ----------- | ---------- | -------------- | +| **Binary Search** | `search()` / `findMedianSortedArrays()` | O(log n) | O(1) | Python, TS, JS | +| **Mathematical** | `Solution.multiply()` / `multiply()` | O(m×n) | O(m+n) | Python, TS, JS | +| **Dynamic Programming** | `countWays()` / `count_ways()` | O(n)〜O(n²) | O(n) | Python, TS, JS | +| **Backtracking** | `combinationSum()` / `backtrack()` | O(2^n) | O(log n) | TS, JS | +| **Data Structures** | `BinaryIndexedTree.update()` / `query()` | O(log n) | O(n) | Python | ```mermaid graph TD @@ -97,10 +97,11 @@ flowchart TD ### 🎮 インタラクティブ機能 -* **リアルタイム実行:** `demoMultiply()` で入力値を即テスト -* **性能計測:** `performance.now()` による時間分析 -* **逐次可視化:** 各ステップを動的追跡 -* **多言語例:** 同一アルゴリズムをPython/TS/JSで比較可能 +**リアルタイム実行:** `demoMultiply()` で入力値を即テスト + +- **性能計測:** `performance.now()` による時間分析 +- **逐次可視化:** 各ステップを動的追跡 +- **多言語例:** 同一アルゴリズムを Python/TS/JS で比較可能 --- @@ -112,7 +113,7 @@ flowchart TD A --> B2[Visualization Engine] A --> B3[Benchmarking Module] A --> B4[Educational UI Components] -``` +``* ` --- @@ -153,3 +154,4 @@ flowchart TD +```