Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Strings / Sequence Dynamic Programming

3.6.4 Palindrome Dynamic Programming

3-Strings/3.6.4_Palindrome_Dynamic_Programming.cpp

Solves basic palindrome dynamic programming problems on a string. The core interval state considers substring $[l, r]$: if the two endpoints match, they can wrap an optimal answer for the inside substring; otherwise, at least one endpoint must be skipped or inserted around the other side. Since each state depends on shorter substrings, intervals are processed by increasing length.

The longest palindromic subsequence (LPS) is a maximum-length subsequence that reads the same forward and backward. The minimum number of insertions needed to make a string a palindrome is the number of characters outside an LPS, so it equals $n$ minus the LPS length.

  • longest_palindromic_subsequence(s) returns one longest palindromic subsequence of string s.
  • min_insertions_palindrome(s) returns the minimum number of characters that must be inserted anywhere in string s to make it a palindrome.

Implementation

#include <algorithm>
#include <cassert>
#include <string>
#include <vector>
using std::string;

string longest_palindromic_subsequence(const string &s) {
  int n = static_cast<int>(s.size());
  if (n == 0) {
    return "";
  }
  std::vector<std::vector<int>> dp(n, std::vector<int>(n));
  for (int len = 1; len <= n; len++) {
    for (int l = 0, r = len - 1; r < n; l++, r++) {
      if (l == r) {
        dp[l][r] = 1;
      } else if (s[l] == s[r]) {
        dp[l][r] = 2 + (l + 1 <= r - 1 ? dp[l + 1][r - 1] : 0);
      } else {
        dp[l][r] = std::max(dp[l + 1][r], dp[l][r - 1]);
      }
    }
  }
  // Optional: reconstruct one longest palindromic subsequence.
  string left, right;
  for (int l = 0, r = n - 1; l <= r;) {
    if (l == r) {
      left += s[l];
      break;
    }
    if (s[l] == s[r] && dp[l][r] == 2 + (l + 1 <= r - 1 ? dp[l + 1][r - 1] : 0)) {
      left += s[l++];
      right += s[r--];
    } else if (dp[l + 1][r] >= dp[l][r - 1]) {
      l++;
    } else {
      r--;
    }
  }
  std::reverse(right.begin(), right.end());
  return left + right;
}

int min_insertions_palindrome(const string &s) {
  int n = static_cast<int>(s.size());
  std::vector<int> dp(n);
  for (int l = n - 1; l >= 0; l--) {
    int inside = 0;
    for (int r = l + 1; r < n; r++) {
      int old = dp[r];
      if (s[l] == s[r]) {
        dp[r] = inside;
      } else {
        dp[r] = 1 + std::min(dp[r], dp[r - 1]);
      }
      inside = old;
    }
  }
  return n == 0 ? 0 : dp[n - 1];
}

Example Usage

#include <cassert>

int main() {
  assert(longest_palindromic_subsequence("bbbab") == "bbbb");
  assert(longest_palindromic_subsequence("character") == "carac");
  assert(min_insertions_palindrome("abcda") == 2);
  assert(min_insertions_palindrome("race") == 3);
  assert(min_insertions_palindrome("") == 0);
  return 0;
}