Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Coding · Dynamic programming

Longest palindromic substring

Lesson · 10 min read · Dynamic programming · Longest palindromic substring

The longest contiguous palindrome. A boolean table over intervals, or the simpler O(1)-space expansion around every centre.

A feasibility table

Now the palindrome must be contiguous. Let pal(i, j) be true if s[i..j] is a palindrome. It is when the ends match and the inside is a palindrome:

Recurrence

pal(i, j)

  1. s[i] = s[j] and (j − i < 2 or pal(i + 1, j − 1)).

answer

  1. the longest interval with pal(i, j) true.

The operators here are and along a choice and no choice at all: this is a feasibility table rather than an optimisation, filled by increasing length like every interval DP.

pal(i, j) for babad, by length. T = true, F = false.

  1. Step 1 / 16
    0b1a2b3a4d0b1a2b3a4d

    Base cases filled first.

  2. Step 2 / 16
    0b1a2b3a4d0bT1a2b3a4d

    [0, 0] "b": a palindrome.

  3. Step 3 / 16
    0b1a2b3a4d0bT1aT2b3a4d

    [1, 1] "a": a palindrome.

  4. Step 4 / 16
    0b1a2b3a4d0bT1aT2bT3a4d

    [2, 2] "b": a palindrome.

  5. Step 5 / 16
    0b1a2b3a4d0bT1aT2bT3aT4d

    [3, 3] "a": a palindrome.

  6. Step 6 / 16
    0b1a2b3a4d0bT1aT2bT3aT4dT

    [4, 4] "d": a palindrome.

  7. Step 7 / 16
    0b1a2b3a4d0bTF1aT2bT3aT4dT

    [0, 1] "ba": not a palindrome.

  8. Step 8 / 16
    0b1a2b3a4d0bTF1aTF2bT3aT4dT

    [1, 2] "ab": not a palindrome.

  9. Step 9 / 16
    0b1a2b3a4d0bTF1aTF2bTF3aT4dT

    [2, 3] "ba": not a palindrome.

  10. Step 10 / 16
    0b1a2b3a4d0bTF1aTF2bTF3aTF4dT

    [3, 4] "ad": not a palindrome.

  11. Step 11 / 16
    0b1a2b3a4d0bTFT1aTF2bTF3aTF4dT

    [0, 2] "bab": a palindrome.

  12. Step 12 / 16
    0b1a2b3a4d0bTFT1aTFT2bTF3aTF4dT

    [1, 3] "aba": a palindrome.

  13. Step 13 / 16
    0b1a2b3a4d0bTFT1aTFT2bTFF3aTF4dT

    [2, 4] "bad": not a palindrome.

  14. Step 14 / 16
    0b1a2b3a4d0bTFTF1aTFT2bTFF3aTF4dT

    [0, 3] "baba": not a palindrome.

  15. Step 15 / 16
    0b1a2b3a4d0bTFTF1aTFTF2bTFF3aTF4dT

    [1, 4] "abad": not a palindrome.

  16. Step 16 / 16
    0b1a2b3a4d0bTFTFF1aTFTF2bTFF3aTF4dT

    [0, 4] "babad": not a palindrome. The longest true interval is [0, 2], "bab" ([1, 3], "aba", is as long; the first is reported).

Expanding around centres

Every palindrome has a centre: a character (odd length) or a gap between two characters (even length). There are 2n − 1 centres. Expanding outwards from each while the ends match finds the longest palindrome around it, using the same fact as the table (matching ends around a palindrome) without storing the table.

def longest_palindrome(s):
    best = (0, 0)                              # [start, end) of the best so far
    for centre in range(2 * len(s) - 1):
        lo, hi = centre // 2, (centre + 1) // 2
        while lo >= 0 and hi < len(s) and s[lo] == s[hi]:
            lo, hi = lo - 1, hi + 1
        if hi - lo - 1 > best[1] - best[0]:
            best = (lo + 1, hi)
    return s[best[0]:best[1]]

Implementation

The problem in this module, solved by expansion in six languages:

def longest_palindrome(s):
    best_lo, best_hi = 0, 1                   # best palindrome is s[best_lo:best_hi]
    for centre in range(2 * len(s) - 1):      # characters and the gaps between them
        lo, hi = centre // 2, (centre + 1) // 2
        while lo >= 0 and hi < len(s) and s[lo] == s[hi]:
            lo -= 1
            hi += 1
        if hi - lo - 1 > best_hi - best_lo:   # strictly longer keeps the earliest
            best_lo, best_hi = lo + 1, hi
    return s[best_lo:best_hi]

The same code is the reference solution to Longest palindromic substring, where it is compiled and run against the tests in all six languages.

TimeO(n²)
SpaceO(1) by expansion; O(n²) for the table

Manacher's algorithm finds it in O(n); it is rarely expected in an interview, but worth naming.