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 subsequence

Lesson · 11 min read · Dynamic programming · Longest palindromic subsequence

The longest subsequence that reads the same both ways. The state is an interval [i, j], and intervals are filled from short to long.

A new kind of state: the interval

In bbbab the subsequence bbbb is a palindrome of length 4. A palindrome is defined by its two ends matching, so the natural subproblem is a stretch of the string: L(i, j), the longest palindromic subsequence inside s[i..j].

Recurrence

L(i, j)

  1. if s[i] = s[j]: 2 + L(i + 1, j − 1) (both ends belong to it);
  2. otherwise max(L(i + 1, j), L(i, j − 1)) (drop one end).

base cases

  1. L(i, i) = 1; an empty interval (i > j) is 0.

An interval reads strictly shorter intervals, so the table is filled by length: all intervals of length 1, then 2, and so on. The answer is L(0, n − 1).

Bottom-up, by length

L(i, j) for bbbab, filled diagonal by diagonal (each diagonal is one length).

  1. Step 1 / 16
    0b1b2b3a4b0b1b2b3a4b

    Base cases filled first.

  2. Step 2 / 16
    0b1b2b3a4b0b11b2b3a4b

    [0, 0] is a single character: 1.

  3. Step 3 / 16
    0b1b2b3a4b0b11b12b3a4b

    [1, 1] is a single character: 1.

  4. Step 4 / 16
    0b1b2b3a4b0b11b12b13a4b

    [2, 2] is a single character: 1.

  5. Step 5 / 16
    0b1b2b3a4b0b11b12b13a14b

    [3, 3] is a single character: 1.

  6. Step 6 / 16
    0b1b2b3a4b0b11b12b13a14b1

    [4, 4] is a single character: 1.

  7. Step 7 / 16
    0b1b2b3a4b0b121b12b13a14b1

    s[0] = s[1] = 'b': 2 + inside = 2.

  8. Step 8 / 16
    0b1b2b3a4b0b121b122b13a14b1

    s[1] = s[2] = 'b': 2 + inside = 2.

  9. Step 9 / 16
    0b1b2b3a4b0b121b122b113a14b1

    'b' ≠ 'a': drop an end, 1.

  10. Step 10 / 16
    0b1b2b3a4b0b121b122b113a114b1

    'a' ≠ 'b': drop an end, 1.

  11. Step 11 / 16
    0b1b2b3a4b0b1231b122b113a114b1

    s[0] = s[2] = 'b': 2 + inside = 3.

  12. Step 12 / 16
    0b1b2b3a4b0b1231b1222b113a114b1

    'b' ≠ 'a': drop an end, 2.

  13. Step 13 / 16
    0b1b2b3a4b0b1231b1222b1133a114b1

    s[2] = s[4] = 'b': 2 + inside = 3.

  14. Step 14 / 16
    0b1b2b3a4b0b12331b1222b1133a114b1

    'b' ≠ 'a': drop an end, 3.

  15. Step 15 / 16
    0b1b2b3a4b0b12331b12232b1133a114b1

    s[1] = s[4] = 'b': 2 + inside = 3.

  16. Step 16 / 16
    0b1b2b3a4b0b123341b12232b1133a114b1

    s[0] = s[4] = 'b': 2 + inside = 4. L(0, 4) = 4: bbbb.

Implementation

In six languages:

def longest_pal_subseq(s):
    n = len(s)
    nxt = [0] * n                        # row i + 1 of the table
    for i in range(n - 1, -1, -1):
        cur = [0] * n
        cur[i] = 1                       # a single character
        for j in range(i + 1, n):
            if s[i] == s[j]:
                cur[j] = 2 + (nxt[j - 1] if i + 1 <= j - 1 else 0)
            else:
                cur[j] = max(nxt[j], cur[j - 1])
        nxt = cur
    return nxt[n - 1]

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

TimeO(n²)
SpaceO(n²), or O(n) keeping two diagonals

A shortcut worth knowing: the longest palindromic subsequence of s equals the longest common subsequence of s and its reverse.