Basics on Dynamic Programming(DP) 5 - Palindrome

字符串处理,Palindrome

1. Palindrome Partitioning II - Leetcode 132 - 边界很难

Given a string s, partition s such that every substring of the partition is a palindrome.
Return the minimum cuts needed for a palindrome partitioning of s.

For example, given s = "aab",
Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.
  • state: f[i] “前i”个字符,用最少几次cut能分割为若干回文子串
  • function: f[i] = min{f[j] + 1, j < i && j+1 ~ i 是一个回文子串}(找“最后”一次cut的位置) ,粗体部分也是DP可以用二维数组记录一下
  • intialize: f[i] = i-1
  • answer: f[s.length()],因为第0个留出来了
//所以需DP的方法 时间复杂度 O(n^2),空间复杂度 O(n^2)
int minCut(string s) {
  int n = s.size();
  vector<vector<bool> > isPalindrome(n, vector<bool>(n, false));  //都是false
  vector<int> dp(n + 1, 0);  //"前i"个字符,用最少几次cut能分割为若干回文子串
  // initialize
  for (int i = 0; i <= n; i++) dp[i] = i - 1;  //第一个f[0]=-1,f1=0

  for (int i = 1; i <= n; i++) {  //从第2个字符开始,边界情况很麻烦
    for (int j = 0; j < i; j++) {  // 1个字符也算
      if (s[j] == s[i - 1] && (i - j < 3 || isPalindrome[j + 1][i - 2])) {
        isPalindrome[j][i - 1] = true;
        dp[i] = min(dp[j] + 1, dp[i]);  //是j-1之前的,加上1
      }
    }
  }
  return dp[n];
}

2. Longest Palindromic Substring - Leetcode 5

Given a string S, find the longest palindromic substring in S.
You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.

传统方法:

string longestPalindrome(string s) {
  //从中间往2边扩散的方法(比从2边向中间走强!走的地方都是有用的)
  //并且回文串有2N-1(N N-1)个这样的中心(中心或者在任意一个字符或者在任意两个字符的之间)n^2
  string ret;
  int length = s.size();
  if (length <= 1) return s;
  // 1个或者2个字符,开始2边扩散! 扫一遍,所有的情况就都包括了
  for (int i = 0; i < length - 1; i++) {
    string res = longestPalindromeCore(s, i, length);
    if (res.size() > ret.size()) ret = res;
  }
  return ret;
}
string longestPalindromeCore(const string &s, int mid, int length) {
  int i;
  //单点扩散
  for (i = 1; mid - i >= 0 && mid + i < length; i++) {
    if (s[mid - i] != s[mid + i]) break;
  }
  //初始化为mid,从mid往2边搜索.注意这里的下标。最后一个参数是长度!!
  string midstr1(s, mid - i + 1, 2 * (i - 1) + 1);
  if (s[mid] != s[mid + 1]) return midstr1;
  //若该点和下个相同,则双点扩散
  for(i=1; mid-i>=0 && mid+1+i<length; i++) {
      if(s[mid-i] != s[mid+1+i])
          break;
  }
  string midstr2(s, mid-i+1, 2*i);
  return (midstr1.size()>midstr2.size()) ? midstr1 : midstr2;
}

DP方法:

string longestPalindrome(string s) {
  //方法2: DP F(i,j) = F(i+1,j-1) if s[i] ==
  // s[j].其中F(i,j)被定义为子串s[i...j]是否是回文
  //所以枚举长度从1~N的子串(O(n2)),再判断是否为回文(O(1)).总体时间复杂度O(n2),
  //空间复杂度O(n2)。
  string ret;
  int length = s.size();
  if (length <= 1) return s;

  bool isPalindrome[1000][1000] = {false};
  int max_len = 1, start = 0;  // 最长回文子串的长度,起点
  for (int i = 0; i < length; i++) {
    isPalindrome[i][i] = true;
    //[j][i]是否是回文  相当于下三角  或者j=i,j<length
    for (int j = 0; j < i; j++) {
      //只有这样,j,i才是回文
      if (s[j] == s[i] && (i - j < 2 || isPalindrome[j + 1][i - 1])) {
        isPalindrome[j][i] = true;  //从小到大
        if (max_len < i - j + 1) {
          max_len = i-j+1;
          start = j;
        }
      }
    }
  }
  return s.substr(start, max_len);
}

另一种O(nlogn)的方法,使用后缀数组,将abcdcb镜面反转加上一个$符号,变为:abcdcb$bcdcba,于是将原问题转换为了求该字符串的所有后缀的最长公共前缀问题。实现较为复杂,可以参考这里

3. 给定字符串,最少插入多少字符,变成回文

  • state: f[i][j] 子串i~j变成回文,最少插入的字符数,
  • function: f[i][j] = if s[i] = s[j]: f[i+1][j-1] else: (1+min{f[i][j-1], f[i+1][j]})
  • intialize: f[i][i] = 0
  • answer: f[0][n-1]
for (i = n - 1; i >= 0; --i) {      // 倒着来,否则i+1没算出来
    for (j = i; j < n; ++j) {
        if (s[i] == s[j])
        	ans[i][j] = ans[i + 1][j - 1];  // 如果两个游标所指字符相同,向中间缩小范围
        else 
        	ans[i][j] = 1 + MIN(ans[i][j - 1], ans[i + 1][j]);  // 如果不同,典型的状态转换方程
    }
}
printf("%d\n", ans[0][n - 1]);
Loading Disqus comments...
Table of Contents