DFS + 強枝刈り による O(1) 実装
数字のみから成る文字列
s
に対し、3つのドットを挿入して4つのセグメントを作り、全ての有効なIPv4アドレスを列挙します。
"0"
は許可)
1 <= s.length <= 20
例1:
Input: s = "25525511135" Output: ["255.255.11.135","255.255.111.35"]
例2:
Input: s = "0000" Output: ["0.0.0.0"]
remainSegs <= remainChars <= remainSegs * 3
で不可能な分岐を排除
'0'
なら長さ1のみ試行
from __future__ import annotations
from typing import List
class Solution:
"""
Restore IP Addresses(メモリ最適化版)
- 0..255 の文字列を事前キャッシュして、部分文字列生成を回避
- 固定長配列 path を再利用し、探索中の一時オブジェクトを最小化
"""
# 共有キャッシュ:0〜255 を文字列化して再利用
_SEG_CACHE: List[str] = [str(i) for i in range(256)]
def restoreIpAddresses(self, s: str) -> List[str]:
"""
全ての有効なIPv4アドレスを列挙
Args:
s: 数字のみから成る文字列
Returns:
生成可能な全ての有効IPv4アドレス(順不同)
Raises:
TypeError: 入力がstrでない、または数字以外を含む場合
Complexity:
Time: O(1)(n≤20、最大3^4分岐、出力を除く)
Space: O(1)(path固定長のみ、出力を除く)
"""
# 入力検証
if not isinstance(s, str):
raise TypeError("Input must be a string.")
n: int = len(s)
# 数字のみ許可
for ch in s:
if ch < '0' or ch > '9':
raise TypeError("Input must contain digits only.")
# IPv4は合計4〜12桁のみ成立
if n < 4 or n > 12:
return []
res: List[str] = []
path: List[str] = [""] * 4 # 固定長配列・再利用
SEG = self._SEG_CACHE # ローカル束縛で属性探索を削減
def dfs(idx: int, seg: int) -> None:
"""
深さ優先探索でセグメントを決定
Args:
idx: 現在の文字位置
seg: 埋まったセグメント数(0〜4)
"""
# 基底条件:4セグメント完成
if seg == 4:
if idx == n:
# 全文字使い切り → 有効なIP
res.append(".".join(path))
return
remain_segs = 4 - seg
remain_chars = n - idx
# 枝刈り:残文字数が不足または過剰
if remain_chars < remain_segs or remain_chars > remain_segs * 3:
return
# 先頭が '0' なら長さ1のみ許可
first_is_zero = s[idx] == '0'
max_len = 1 if first_is_zero else 3
val = 0 # セグメント数値を逐次生成
for length in range(1, max_len + 1):
if idx + length > n:
break
# 逐次数値化:val = val*10 + digit
val = val * 10 + (ord(s[idx + length - 1]) - 48)
# 255超過したら以降は全て不正
if val > 255:
break
# キャッシュから文字列参照(スライス生成なし)
path[seg] = SEG[val]
dfs(idx + length, seg + 1)
dfs(0, 0)
return res
フローの説明:
1. 基底条件:4セグメント完成 + 全文字使用 → 結果に追加
2. 枝刈り:残文字数が不足/過剰なら即座に戻る
3. 長さ決定:先頭が '0' なら長さ1のみ、それ以外は1〜3を試行
4. 逐次数値化:ループ内で桁を加算(int()変換を回避)
5. 255チェック:超過したらループ脱出(以降は全て不正)
6. 再帰呼び出し:キャッシュから文字列参照し、次のセグメントへ
7. ループバック:次の長さを試行、全て試したら前のステップへ戻る
| 指標 | 値 | 備考 |
|---|---|---|
| 時間計算量 | O(1) | 入力長 n≤20、各セグメント1〜3桁で最大3^4=81分岐だが、枝刈りで実際は大幅削減。出力サイズを除けば定数時間 |
| 空間計算量 | O(1) | 固定長配列 path[4] のみ使用。再帰深さ4も定数。キャッシュはクラス変数で共有 |
| 最適化手法 | キャッシュ + 枝刈り | スライス生成ゼロ、逐次数値化、残文字数の上下限チェック |
| アプローチ | 時間 | 空間 | 備考 |
|---|---|---|---|
| DFS + 強枝刈り(本実装) | O(1) | O(1) | 最速。残文字数/先頭0/255超で剪定 |
| 3ドット全列挙(i<j<k) | O(n³) | O(1) | 実装容易だが条件判定が散在 |
| BFS 層展開 | O(1) | O(k) | 中間配列が増えGC圧上昇 |