アルゴリズム概要

問題説明

文字列 s が与えられたとき、最長の回文部分文字列を返す問題です。

入出力例

例1:

Input: s = "babad"
Output: "bab" (または "aba")

例2:

Input: s = "cbbd"
Output: "bb"

制約条件

戦略

主要ポイント

時間計算量: O(n²)

各位置 O(n) × 展開 O(n) = O(n²)

空間計算量: O(1)

インデックスのみ保持、追加構造不要

ステップバイステップ解説

s = "babad" を例に、アルゴリズムの動作を追ってみましょう。

Python実装

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]

フローチャート

開始 n <= 1 はい s を返す (基底条件) いいえ 初期化 best_l = 0, best_r = 0 i < n (各位置を走査) はい expand(i, i) (奇数長) より長い 回文か はい best を 更新 いいえ expand(i, i+1) (偶数長) i++ いいえ 結果を 返す

フローの説明:
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: ◎ (最速)

💡 採用理由

  • 制約 n ≤ 1000 では O(n²) で十分実用的
  • 追加メモリ O(1) でメモリ効率が最高
  • 実装が短く、バグ混入率が低い
  • CPython の文字比較(C実装)を活用できる