|
| 1 | +# Reverse Integer - 32bit符号付き整数の桁反転 |
| 2 | + |
| 3 | +<h2 id="toc">目次</h2> |
| 4 | + |
| 5 | +- [概要](#overview) |
| 6 | +- [アルゴリズム要点(TL;DR)](#tldr) |
| 7 | +- [図解](#figures) |
| 8 | +- [正しさのスケッチ](#correctness) |
| 9 | +- [計算量](#complexity) |
| 10 | +- [Python実装](#impl) |
| 11 | +- [CPython最適化ポイント](#cpython) |
| 12 | +- [エッジケースと検証観点](#edgecases) |
| 13 | +- [FAQ](#faq) |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +<h2 id="overview">概要</h2> |
| 18 | + |
| 19 | +**問題**: 32bit符号付き整数 `x` の桁を反転し、結果が `[-2³¹, 2³¹-1]` の範囲外になる場合は `0` を返す。64bit整数の使用は禁止。 |
| 20 | + |
| 21 | +**要件**: |
| 22 | + |
| 23 | +- 正当性: 桁を正しく反転し、オーバーフロー時は `0` を返す |
| 24 | +- 制約: 空間 O(1) が理想だが、Python では文字列法が実測で最速 |
| 25 | +- 安定性: 符号を正しく保持し、先頭ゼロを自然に削除 |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +<h2 id="tldr">アルゴリズム要点(TL;DR)</h2> |
| 30 | + |
| 31 | +**戦略**: Pythonの高速な組み込み文字列処理を活用 |
| 32 | + |
| 33 | +- **文字列反転法**: `str()` → スライス `[::-1]` → `int()` で一括処理 |
| 34 | +- **符号分離**: 絶対値を反転後に符号を掛け戻し |
| 35 | +- **範囲チェック**: 最後に32bit境界と比較 |
| 36 | + |
| 37 | +**データ構造**: 文字列(一時)、整数定数 |
| 38 | +**時間計算量**: O(d) — d は桁数(最大10) |
| 39 | +**空間計算量**: O(d) — 文字列スライスで一時領域 |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +<h2 id="figures">図解</h2> |
| 44 | + |
| 45 | +## フローチャート |
| 46 | + |
| 47 | +```mermaid |
| 48 | +flowchart TD |
| 49 | + Start[Start reverse] --> OneDigit{-9 <= x <= 9} |
| 50 | + OneDigit -- Yes --> ReturnX[Return x] |
| 51 | + OneDigit -- No --> ExtractSign[Extract sign and abs] |
| 52 | + ExtractSign --> StrReverse[String reverse via slice] |
| 53 | + StrReverse --> ToInt[Convert back to int] |
| 54 | + ToInt --> ApplySign[Apply original sign] |
| 55 | + ApplySign --> RangeCheck{INT_MIN <= res <= INT_MAX} |
| 56 | + RangeCheck -- No --> ReturnZero[Return 0] |
| 57 | + RangeCheck -- Yes --> ReturnRes[Return res] |
| 58 | +``` |
| 59 | + |
| 60 | +**説明**: 1桁の場合は即座に返し、それ以外は符号を分離して絶対値の文字列を反転。整数化後に符号を戻し、32bit範囲外なら `0` を返す。 |
| 61 | + |
| 62 | +### データフロー図 |
| 63 | + |
| 64 | +```mermaid |
| 65 | +graph LR |
| 66 | + subgraph Input_Phase |
| 67 | + A[Input x] --> B[Early return check] |
| 68 | + end |
| 69 | + subgraph Processing |
| 70 | + B --> C[Sign extraction] |
| 71 | + C --> D[Absolute value to string] |
| 72 | + D --> E[Slice reverse] |
| 73 | + E --> F[Parse to int] |
| 74 | + end |
| 75 | + subgraph Output_Phase |
| 76 | + F --> G[Apply sign] |
| 77 | + G --> H[Range validation] |
| 78 | + H --> I[Return result or 0] |
| 79 | + end |
| 80 | +``` |
| 81 | + |
| 82 | +**説明**: 入力を早期チェック後、符号分離・文字列反転・整数化・符号適用・範囲検証の順で処理し、結果を出力。 |
| 83 | + |
| 84 | +--- |
| 85 | + |
| 86 | +<h2 id="correctness">正しさのスケッチ</h2> |
| 87 | + |
| 88 | +**不変条件**: |
| 89 | + |
| 90 | +- 符号は分離保存され、最後に正しく適用される |
| 91 | +- 文字列スライスは先頭ゼロを自動削除(`int()` が処理) |
| 92 | + |
| 93 | +**網羅性**: |
| 94 | + |
| 95 | +- 1桁: 早期リターンで正しく返す |
| 96 | +- 複数桁: 文字列反転で全桁を逆順に処理 |
| 97 | +- 負数: 符号を分離して絶対値処理後に `-1` を掛ける |
| 98 | + |
| 99 | +**基底条件**: `-9 ≤ x ≤ 9` の場合、桁反転は自身なので即座に返す |
| 100 | + |
| 101 | +**終了性**: 文字列操作は有限桁(最大10桁)で必ず終了 |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +<h2 id="complexity">計算量</h2> |
| 106 | + |
| 107 | +**時間計算量**: **O(d)** |
| 108 | + |
| 109 | +- d は入力整数の桁数(最大10) |
| 110 | +- `str()`, スライス `[::-1]`, `int()` はすべて O(d) |
| 111 | +- CPythonのC実装により、ループより高速 |
| 112 | + |
| 113 | +**空間計算量**: **O(d)** |
| 114 | + |
| 115 | +- 文字列スライスで一時的に O(d) のメモリを使用 |
| 116 | +- 最終結果は整数1つ |
| 117 | + |
| 118 | +**比較**: 数値のみの O(1) 空間版も可能だが、Pythonでは文字列法の方が実測で速い |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +<h2 id="impl">Python実装</h2> |
| 123 | + |
| 124 | +```python |
| 125 | +from __future__ import annotations |
| 126 | + |
| 127 | +class Solution: |
| 128 | + """ |
| 129 | + Reverse Integer (LeetCode #7) |
| 130 | + 32-bit 符号付き整数 x の数字を反転。範囲外は 0 を返す。 |
| 131 | + """ |
| 132 | + |
| 133 | + # 32bit境界定数(クラス定数として明示) |
| 134 | + INT_MAX: int = 2_147_483_647 # 2^31 - 1 |
| 135 | + INT_MIN: int = -2_147_483_648 # -2^31 |
| 136 | + |
| 137 | + def reverse(self, x: int) -> int: |
| 138 | + """ |
| 139 | + 文字列反転法による最速実装 |
| 140 | +
|
| 141 | + Args: |
| 142 | + x: 32-bit signed integer |
| 143 | +
|
| 144 | + Returns: |
| 145 | + 反転後の整数(範囲外は 0) |
| 146 | +
|
| 147 | + Time: O(d), Space: O(d) — d は桁数(最大10) |
| 148 | + """ |
| 149 | + # 基底条件: 1桁はそのまま返す(早期リターン) |
| 150 | + if -9 <= x <= 9: |
| 151 | + return x |
| 152 | + |
| 153 | + # 符号を分離して絶対値の文字列を反転 |
| 154 | + sign: int = -1 if x < 0 else 1 |
| 155 | + abs_x: int = -x if x < 0 else x |
| 156 | + |
| 157 | + # C実装の高速な文字列処理を活用 |
| 158 | + # [::-1] スライスで反転、int() で整数化(先頭ゼロは自動削除) |
| 159 | + rev_abs: int = int(str(abs_x)[::-1]) |
| 160 | + |
| 161 | + # 符号を適用 |
| 162 | + result: int = rev_abs * sign |
| 163 | + |
| 164 | + # 32bit範囲チェック(Pythonの任意精度intを手動で拘束) |
| 165 | + if result < self.INT_MIN or result > self.INT_MAX: |
| 166 | + return 0 |
| 167 | + |
| 168 | + return result |
| 169 | +``` |
| 170 | + |
| 171 | +**主要ステップ**: |
| 172 | + |
| 173 | +1. **早期リターン**: 1桁の場合は処理不要 |
| 174 | +2. **符号分離**: 負数を絶対値化して `sign` に記録 |
| 175 | +3. **文字列反転**: `str()[::-1]` でC実装の高速処理 |
| 176 | +4. **整数化**: `int()` で先頭ゼロを自動削除 |
| 177 | +5. **符号適用**: `sign` を掛けて元の符号を復元 |
| 178 | +6. **範囲検証**: 32bit境界外なら `0` を返す |
| 179 | + |
| 180 | +--- |
| 181 | + |
| 182 | +<h2 id="cpython">CPython最適化ポイント</h2> |
| 183 | + |
| 184 | +**採用した最適化**: |
| 185 | + |
| 186 | +- **文字列処理の活用**: CPythonの `str()`, スライス, `int()` はC実装で高速 |
| 187 | +- **早期リターン**: 1桁の場合は条件分岐を避けて即座に返す |
| 188 | +- **符号分離**: 負数の `%` 演算を回避し、分岐を削減 |
| 189 | + |
| 190 | +**追加の最適化余地**: |
| 191 | + |
| 192 | +- **定数のローカル化**: `INT_MAX` 等を関数内ローカル変数に束縛すると属性参照を削減できる(マイクロ最適化) |
| 193 | + |
| 194 | +```python |
| 195 | +int_max = self.INT_MAX |
| 196 | +int_min = self.INT_MIN |
| 197 | +``` |
| 198 | + |
| 199 | +- **数値のみ版**: O(1) 空間が必須なら `divmod` を使った桁処理も可能だが、Pythonでは文字列法が速い |
| 200 | + |
| 201 | +**GIL影響**: 単一スレッドのCPU計算のみで影響なし |
| 202 | + |
| 203 | +--- |
| 204 | + |
| 205 | +<h2 id="edgecases">エッジケースと検証観点</h2> |
| 206 | + |
| 207 | +| ケース | 入力例 | 期待出力 | 検証ポイント | |
| 208 | +| ------------ | ------------- | -------- | ----------------------- | |
| 209 | +| 1桁正数 | `5` | `5` | 早期リターン | |
| 210 | +| 1桁負数 | `-5` | `-5` | 符号保持 | |
| 211 | +| 末尾ゼロ | `120` | `21` | 先頭ゼロ削除 | |
| 212 | +| 負数末尾ゼロ | `-120` | `-21` | 符号+ゼロ削除 | |
| 213 | +| 正の境界 | `2147483647` | `0` | 上限オーバーフロー | |
| 214 | +| 負の境界 | `-2147483648` | `0` | 下限オーバーフロー | |
| 215 | +| 境界直前 | `1534236469` | `0` | `9646324351` は上限超え | |
| 216 | +| ゼロ | `0` | `0` | 基底条件 | |
| 217 | +| 複数桁正数 | `123` | `321` | 通常の反転 | |
| 218 | +| 複数桁負数 | `-123` | `-321` | 符号保持 | |
| 219 | + |
| 220 | +**検証方法**: |
| 221 | + |
| 222 | +- 単体テスト: 上記の全ケースをアサーション |
| 223 | +- 境界値テスト: `±2³¹` 付近の値 |
| 224 | +- ランダムテスト: `-2³¹` から `2³¹-1` の範囲でランダム生成 |
| 225 | + |
| 226 | +--- |
| 227 | + |
| 228 | +<h2 id="faq">FAQ</h2> |
| 229 | + |
| 230 | +**Q1: なぜ数値のみの O(1) 空間版ではなく文字列法を採用したのか?** |
| 231 | +A: CPythonでは `str()`, スライス, `int()` がC実装で最適化されており、桁数が最大10と小さいため、文字列法の方が実測で速いケースが多い。競技プログラミングでは実行時間が優先される。 |
| 232 | + |
| 233 | +**Q2: 64bit整数を使えば簡単では?** |
| 234 | +A: 問題制約で64bit整数の使用は禁止されている。Pythonは任意精度intだが、32bit範囲を手動でチェックする必要がある。 |
| 235 | + |
| 236 | +**Q3: 負数の剰余演算を避ける理由は?** |
| 237 | +A: Pythonの `%` は負数で数学的定義(floor division)に従うため、C言語的な「toward-zero」と異なる。符号分離して絶対値で処理する方が分岐が少なく、読みやすい。 |
| 238 | + |
| 239 | +**Q4: 先頭ゼロはどう処理されるのか?** |
| 240 | +A: `int("021")` は自動的に `21` になるため、特別な処理は不要。 |
| 241 | + |
| 242 | +**Q5: オーバーフロー検査を事前に行わない理由は?** |
| 243 | +A: Pythonの任意精度intでは反転後の値を一旦計算してから範囲チェックする方がシンプル。数値のみ版では事前検査が必要だが、文字列法では最後に一発で判定できる。 |
| 244 | + |
| 245 | +**Q6: 業務開発で使う場合の注意点は?** |
| 246 | +A: 型検証(`isinstance(x, int)`)や範囲検証を追加し、例外処理を実装する。LeetCode版は速度優先で検証を省略している。 |
0 commit comments