Skip to content

5. Longest Palindromic Substring

class Solution:
    """🪞 Longest Palindromic Substring.

    Problem:
    --------
    Given a string `s` (digits and English letters, `len(s) <= 1000`), return the longest
    palindromic substring in `s`.

    Example (`s = "babad"`, answer `"bab"`; `"aba"` is also valid).
    Example (`s = "cbbd"`, answer `"bb"`).

    Key Insight:
    ------------
    Checking one string is easy: two pointers from the ends, stop at the first mismatch.
    But we need the longest over all substrings, so instead grow palindromes **outward
    from their center**: <--lo-- center --hi--> until the chars differ or a border is hit.

    Every palindrome has exactly one center, and there are two kinds:
    - a character, for odd lengths (`"aba"`): start with `expand(i, i)`
    - a gap between two characters, for even lengths (`"abba"`): start with
      `expand(i, i + 1)`

    That gives `2n - 1` centers, and trying all of them covers every palindrome.
    Same expansion as 647 (Palindromic Substrings), but tracking the longest instead of
    counting.

    Approach:
    ---------
    - When the loop stops, `lo` and `hi` are one step **past** the palindrome, so it
      spans `s[lo + 1:hi]` with length `hi - lo - 1`.
    - An even center with `s[i] != s[i + 1]` returns length 0, so it never wins.

    Complexity:
    -----------
    - Time: O(n^2), `2n - 1` centers, each expansion O(n)
    - Space: O(1), apart from the returned substring
    """

    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        best_start = 0
        max_len = 0

        def expand(lo: int, hi: int) -> tuple[int, int]:
            while lo >= 0 and hi < n and s[lo] == s[hi]:
                lo -= 1
                hi += 1

            start = lo + 1
            seq_len = hi - lo - 1
            return start, seq_len

        for pivot in range(n):
            # Odd-length palindromes
            start1, len1 = expand(pivot, pivot)

            # Even-length palindromes
            start2, len2 = expand(pivot, pivot + 1)

            if len1 > max_len:
                best_start = start1
                max_len = len1

            if len2 > max_len:
                best_start = start2
                max_len = len2

        return s[best_start : best_start + max_len]