|
| 1 | +## 1. 問題分析結果 |
| 2 | + |
| 3 | +## 競技プログラミング視点 |
| 4 | + |
| 5 | +- 制約 `0 ≤ x ≤ 2³¹ - 1` → 探索空間は最大 `~46340` → **二分探索で最大31回**のイテレーションで確定 |
| 6 | +- Python の `int` は任意精度 → オーバーフロー完全ゼロ・キャスト不要(Rust/TSと異なる大きな利点) |
| 7 | +- `x >> 1` でビットシフト整数除算 → CPython の `BINARY_OP` 最適化が効く |
| 8 | +- `math.sqrt` / `**` / `pow` は使用禁止 → 純粋な整数演算のみで完結 |
| 9 | + |
| 10 | +## 業務開発視点 |
| 11 | + |
| 12 | +- 入力は `int` だが、`float`・`str`・負数が混入する可能性 → **実行時型ガード**が必要 |
| 13 | +- `pylance` 対応: 戻り値型 `int` を明示し、`isinstance` ガードで型を narrowing |
| 14 | +- `ValueError` / `TypeError` を使い分けて呼び出し元が例外種別で判断できるように設計 |
| 15 | + |
| 16 | +## Python特有分析 |
| 17 | + |
| 18 | +- **データ構造**: 変数3つ(`low`, `high`, `mid`)のみ → `list`/`deque` 不要 |
| 19 | +- **標準ライブラリ活用**: `math` モジュールは使用禁止のため不使用。ビットシフト演算子のみ |
| 20 | +- **CPython最適化**: `while` ループ + ビットシフト → 組み込み演算子レベルで最速 |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## 2. アルゴリズムアプローチ比較 |
| 25 | + |
| 26 | +| アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 | |
| 27 | +| ----------------- | ------------ | ---------- | ---------------- | ------ | ------------------ | ------------- | ----------------------- | |
| 28 | +| **線形探索** | O(√n) | O(1) | 低 | ★★★ | なし | 適 | x=2³¹で~46340回、非効率 | |
| 29 | +| **二分探索** | O(log n) | O(1) | 低 | ★★★ | なし | ✅ 適 | 整数演算のみ・誤差ゼロ | |
| 30 | +| **ニュートン法** | O(log log n) | O(1) | 中 | ★★☆ | なし | 不適 | float収束判定が曖昧 | |
| 31 | +| **`isqrt()`使用** | O(log n) | O(1) | 最低 | ★★★ | `math.isqrt` | 適 | 問題の禁止制約に抵触 | |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +## 3. 採用アルゴリズムと根拠 |
| 36 | + |
| 37 | +- **選択**: 二分探索(Binary Search) |
| 38 | +- **理由**: O(log n)・整数演算のみ・Python の任意精度 `int` でオーバーフロー皆無・Loop Invariant が明確で保守性最高 |
| 39 | +- **Python最適化戦略**: `x >> 1`(ビットシフト)+ `(low + high) >> 1`(中点計算)で CPython のバイトコード最適化を最大活用 |
| 40 | +- **トレードオフ**: ニュートン法は収束が速いが `float` の誤差管理が複雑 → 保守性 vs わずかな速度差で二分探索を選択 |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## 4. 実装コード |
| 45 | + |
| 46 | +```python |
| 47 | +# Runtime 3 ms |
| 48 | +# Beats 66.79% |
| 49 | +# Memory 19.32 MB |
| 50 | +# Beats 57.57% |
| 51 | + |
| 52 | +class Solution: |
| 53 | + """ |
| 54 | + Sqrt(x) — 整数平方根(切り捨て) |
| 55 | + math.sqrt / ** 演算子禁止・二分探索で実装 |
| 56 | + """ |
| 57 | + |
| 58 | + # ------------------------------------------------------------------ # |
| 59 | + # 業務開発版(型安全・エラーハンドリング・pylance 対応) |
| 60 | + # ------------------------------------------------------------------ # |
| 61 | + def mySqrt(self, x: int) -> int: |
| 62 | + """ |
| 63 | + 非負整数 x の平方根を小数点以下切り捨てで返す。 |
| 64 | +
|
| 65 | + Args: |
| 66 | + x: 非負整数 (0 ≤ x ≤ 2^31 - 1) |
| 67 | +
|
| 68 | + Returns: |
| 69 | + floor(√x) の整数値 |
| 70 | +
|
| 71 | + Raises: |
| 72 | + TypeError: x が int でない場合 |
| 73 | + ValueError: x が負数または制約超過の場合 |
| 74 | +
|
| 75 | + Complexity: |
| 76 | + Time: O(log n) — 最大 31 回のイテレーション |
| 77 | + Space: O(1) — 固定変数のみ、追加アロケーションなし |
| 78 | + """ |
| 79 | + # ── 実行時型ガード(pylance narrowing 対応) ────────────────── |
| 80 | + if not isinstance(x, int) or isinstance(x, bool): |
| 81 | + raise TypeError(f"x must be int, got {type(x).__name__!r}") |
| 82 | + |
| 83 | + if x < 0: |
| 84 | + raise ValueError(f"x must be non-negative, got {x}") |
| 85 | + |
| 86 | + if x > 2**31 - 1: |
| 87 | + raise ValueError(f"x={x} exceeds constraint 2^31 - 1") |
| 88 | + |
| 89 | + return self._binary_search_sqrt(x) |
| 90 | + |
| 91 | + def _binary_search_sqrt(self, x: int) -> int: |
| 92 | + """ |
| 93 | + 二分探索による整数平方根の計算(内部実装)。 |
| 94 | +
|
| 95 | + Loop Invariant: |
| 96 | + low - 1 の二乗は x 以下 |
| 97 | + high + 1 の二乗は x より大きい |
| 98 | + → ループ終了時: high = floor(√x) |
| 99 | +
|
| 100 | + Args: |
| 101 | + x: 検証済み非負整数 |
| 102 | +
|
| 103 | + Returns: |
| 104 | + floor(√x) |
| 105 | + """ |
| 106 | + # ── エッジケース早期リターン ────────────────────────────────── |
| 107 | + # x=0 → 0、x=1 → 1(ループを回さず即リターン) |
| 108 | + if x < 2: |
| 109 | + return x |
| 110 | + |
| 111 | + # ── 二分探索 ───────────────────────────────────────────────── |
| 112 | + # 探索範囲: [1, x // 2] |
| 113 | + # 根拠: x >= 2 のとき floor(√x) <= x // 2 が常に成立 |
| 114 | + low: int = 1 |
| 115 | + high: int = x >> 1 # == x // 2(ビットシフトで整数除算) |
| 116 | + |
| 117 | + while low <= high: |
| 118 | + # オーバーフロー不要(Python の int は任意精度)だが |
| 119 | + # ビットシフトで中点計算 → CPython バイトコード最適化 |
| 120 | + mid: int = (low + high) >> 1 |
| 121 | + square: int = mid * mid |
| 122 | + |
| 123 | + if square == x: |
| 124 | + # 完全平方数: mid が答えそのもの |
| 125 | + return mid |
| 126 | + elif square < x: |
| 127 | + # mid が小さすぎる → 下限を引き上げ |
| 128 | + low = mid + 1 |
| 129 | + else: |
| 130 | + # mid が大きすぎる → 上限を引き下げ |
| 131 | + high = mid - 1 |
| 132 | + |
| 133 | + # ループ終了後: high = floor(√x) |
| 134 | + # 例: x=8 → mid=2(4<8)→low=3, mid=3(9>8)→high=2 |
| 135 | + # low(3) > high(2) → return 2 ✓ |
| 136 | + return high |
| 137 | + |
| 138 | + # ------------------------------------------------------------------ # |
| 139 | + # 競技プログラミング版(型チェック・docstring 省略・速度最優先) |
| 140 | + # ------------------------------------------------------------------ # |
| 141 | + def mySqrt_competitive(self, x: int) -> int: |
| 142 | + """ |
| 143 | + Competitive: O(log n) / O(1) |
| 144 | + エラーハンドリング省略・CPython 最速パターン |
| 145 | + """ |
| 146 | + if x < 2: |
| 147 | + return x |
| 148 | + |
| 149 | + low, high = 1, x >> 1 |
| 150 | + |
| 151 | + while low <= high: |
| 152 | + mid = (low + high) >> 1 |
| 153 | + sq = mid * mid |
| 154 | + if sq == x: |
| 155 | + return mid |
| 156 | + elif sq < x: |
| 157 | + low = mid + 1 |
| 158 | + else: |
| 159 | + high = mid - 1 |
| 160 | + |
| 161 | + return high |
| 162 | +``` |
| 163 | + |
| 164 | +--- |
| 165 | + |
| 166 | +## 5. アルゴリズム動作トレース |
| 167 | + |
| 168 | +``` |
| 169 | +─── x = 4 ───────────────────────────────────────────────────── |
| 170 | + 初期値: low=1, high=2 |
| 171 | +
|
| 172 | + Iter 1: mid=1, sq=1 < 4 → low=2 |
| 173 | + Iter 2: mid=2, sq=4 == 4 → return 2 ✓ |
| 174 | +
|
| 175 | +─── x = 8 ───────────────────────────────────────────────────── |
| 176 | + 初期値: low=1, high=4 |
| 177 | +
|
| 178 | + Iter 1: mid=2, sq=4 < 8 → low=3 |
| 179 | + Iter 2: mid=3, sq=9 > 8 → high=2 |
| 180 | + low(3) > high(2) → return high=2 ✓ |
| 181 | +
|
| 182 | +─── エッジケース ──────────────────────────────────────────────── |
| 183 | + x=0 → early return 0 |
| 184 | + x=1 → early return 1 |
| 185 | + x=2 → low=1,high=1 → mid=1(1<2)→low=2 → 終了: return 1 ✓ |
| 186 | + x=2147483647 → return 46340 |
| 187 | +``` |
| 188 | + |
| 189 | +--- |
| 190 | + |
| 191 | +## 6. Python固有の設計ポイント整理 |
| 192 | + |
| 193 | +| 観点 | 実装での対応 | |
| 194 | +| -------------------------- | --------------------------------------------------------------------------- | |
| 195 | +| **オーバーフロー完全ゼロ** | Python `int` は任意精度 → `u64` キャスト(Rust)が不要 | |
| 196 | +| **型narrowing (pylance)** | `isinstance(x, int) and not isinstance(x, bool)` で `bool` 混入も排除 | |
| 197 | +| **ビットシフト最適化** | `x >> 1` / `(low+high) >> 1` → CPython `BINARY_OP` 命令で最速 | |
| 198 | +| **`bool` ガード** | Python では `bool` は `int` のサブクラス → `isinstance(x, bool)` で明示排除 | |
| 199 | +| **2パターン分離** | 業務版(型安全)と競技版(速度最優先)を明確に分離し保守性向上 | |
0 commit comments