中心展開法による O(n²) 実装
文字列
s
が与えられたとき、最長の回文部分文字列を返す問題です。
例1:
Input: s = "babad"
Output: "bab" (または "aba")
例2:
Input: s = "cbbd"
Output: "bb"
1 <= s.length <= 1000
s
は英字と数字のみから構成される
時間計算量: O(n²)
各位置 O(n) × 展開 O(n) = O(n²)
空間計算量: O(1)
インデックスのみ保持、追加構造不要
s = "babad" を例に、アルゴリズムの動作を追ってみましょう。
from __future__ import annotations
class Solution:
def longestPalindrome(self, s: str) -> str:
"""
Longest Palindromic Substring - 中心展開法
Time: O(n^2), Space: O(1)
各位置を中心に奇数長・偶数長の回文を展開し、最長のものを返す。
"""
n: int = len(s)
if n <= 1:
return s
def expand(l: int, r: int) -> tuple[int, int]:
"""
中心 (l, r) から可能な限り展開し、
inclusive の [L, R] インデックスを返す。
"""
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
# ループ終了時は l, r が条件を満たさない位置なので +1, -1 で戻す
return l + 1, r - 1
best_l: int = 0
best_r: int = 0
# 各位置で奇数長・偶数長の両方を試行
for i in range(n):
# 奇数長(中心1文字)
l1, r1 = expand(i, i)
if r1 - l1 > best_r - best_l:
best_l, best_r = l1, r1
# 偶数長(中心2文字)
l2, r2 = expand(i, i + 1)
if r2 - l2 > best_r - best_l:
best_l, best_r = l2, r2
# 最後に一度だけスライスを生成(メモリ効率化)
return s[best_l : best_r + 1]
フローの説明:
1. 開始 → 文字列 s の長さをチェック
2. n ≤ 1 なら
s をそのまま返して終了(基底条件)
3. そうでなければ
best_l = 0, best_r = 0
で初期化
4. 各位置 i を走査(i = 0 から n-1 まで)
5. 各位置で
expand(i, i) を実行(奇数長)
6. より長い回文なら best を更新
7. 次に
expand(i, i+1)
を実行(偶数長)
8. より長い回文なら best を更新
9. i++ して次の位置へ(ステップ4に戻る)
10. 全位置走査完了後、s[best_l : best_r+1] を返して終了
| 項目 | 本実装(中心展開法) | 代替手法 |
|---|---|---|
| 時間計算量 |
O(n²)
|
DP: O(n²) Manacher: O(n) |
| 空間計算量 |
O(1)
|
DP: O(n²) Manacher: O(n) |
| 実装コスト | 低 (短く直感的) |
DP: 中 Manacher: 高 |
| n=1000での実用性 | ◎ 十分高速 |
DP: △ (メモリ重い) Manacher: ◎ (最速) |
💡 採用理由