二分探索(Binary Search)による O(log n) 実装
非負整数
x を受け取り、
floor(√x)(小数点以下切り捨て)を返す。
math.sqrt・ **・
pow(x, 0.5)
などの組み込み指数演算は使用禁止。
0 ≤ x ≤ 2³¹ - 1(非負整数)
math.sqrt
禁止
**
演算子 禁止
pow(x, 0.5)
禁止
class Solution:
def mySqrt(self, x: int) -> int:
# エッジケース: x=0, x=1 は即リターン
if x < 2:
return x
# 探索範囲: [1, x // 2]
# 根拠: x >= 2 のとき floor(√x) <= x // 2 が常に成立
low: int = 1
high: int = x >> 1 # ビットシフトで x // 2(CPython 最速)
# Loop Invariant:
# (low - 1)^2 <= x かつ (high + 1)^2 > x
# → ループ終了時: high = floor(√x)
while low <= high:
mid: int = (low + high) >> 1 # 中点計算
sq: int = mid * mid # Python int は任意精度 → オーバーフローなし
if sq == x:
return mid # 完全平方数: 即リターン
elif sq < x:
low = mid + 1 # mid が小さすぎる → 下限を引き上げ
else:
high = mid - 1 # mid が大きすぎる → 上限を引き下げ
# ループ終了後: high = floor(√x)
return high
graph TD
Start["開始: mySqrt(x)"]
CheckEdge{"x < 2?"}
ReturnX["return x"]
Init["初期化: low=1, high=x/2"]
LoopCheck{"low <= high?"}
ReturnHigh["return high"]
CalcMid["中点計算
mid=(low+high)/2"]
Compare{"sq vs x"}
ReturnMid["return mid"]
UpdateLow["low=mid+1"]
UpdateHigh["high=mid-1"]
End["終了"]
Start --> CheckEdge
CheckEdge -->|Yes| ReturnX
CheckEdge -->|No| Init
Init --> LoopCheck
LoopCheck -->|No| ReturnHigh
LoopCheck -->|Yes| CalcMid
CalcMid --> Compare
Compare -->|Equal| ReturnMid
Compare -->|Less| UpdateLow
Compare -->|Greater| UpdateHigh
UpdateLow --> LoopCheck
UpdateHigh --> LoopCheck
ReturnX --> End
ReturnMid --> End
ReturnHigh --> End
style Start fill:#d1fae5,stroke:#10b981,stroke-width:3px
style End fill:#d1fae5,stroke:#10b981,stroke-width:3px
style CheckEdge fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
style LoopCheck fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
style Compare fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
style ReturnX fill:#d1fae5,stroke:#059669,stroke-width:2px
style ReturnMid fill:#d1fae5,stroke:#059669,stroke-width:2px
style ReturnHigh fill:#d1fae5,stroke:#059669,stroke-width:2px
style Init fill:#e0f2fe,stroke:#0284c7,stroke-width:2px
style CalcMid fill:#e0f2fe,stroke:#0284c7,stroke-width:2px
style UpdateLow fill:#e0f2fe,stroke:#0284c7,stroke-width:2px
style UpdateHigh fill:#fee2e2,stroke:#dc2626,stroke-width:2px
フローの説明:
1. エッジケース判定: x < 2 の場合は x をそのまま返す(0→0,
1→1)
2. 探索範囲初期化: low=1,
high=x>>1(x/2)で探索上限を半分に削減
3. 二分探索ループ: low>high になるまで
mid=(low+high)>>1 を計算
4. 三方比較: sq==x(即リターン)/ sq<x(low引き上げ)/
sq>x(high引き下げ)
5. ループ終了: high = floor(√x) が確定 → return high
── 紫の破線:
ループバック(探索範囲を狭めて次のイテレーションへ)
| アプローチ | 時間 | 空間 | 誤差 | 保守性 | 選択 |
|---|---|---|---|---|---|
| 線形探索 | O(√n) | O(1) | なし | ★★★ | ✗ 遅い |
| 二分探索 ✅ | O(log n) | O(1) | なし | ★★★ | ✅ 最適 |
| ニュートン法 | O(log log n) | O(1) | float誤差 | ★★☆ | △ 誤差リスク |
| math.isqrt() | O(log n) | O(1) | なし | ★★★ | 🚫 禁止 |