Median of Two Sorted Arrays

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

📋 アルゴリズム概要

2つのソート済み配列 nums1nums2 が与えられたとき、それらをマージした際の中央値を求める問題です。 要件として 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)