最长回文子串

来源:互联网 发布:linux 777 什么权限 编辑:程序博客网 时间:2024/05/31 18:53

注意, substring和subsequence 是有区别的。substring属于subsequence, 但是subsequence 不一定是substring。

给定一个string T = str[0...n-1], 该字符串的一个子串substring就是截取连续的(包括全部)的一部分subT = [i, ...j], 其中, 0<= i and j <= n-1。 但是subsequence则是选择相关的字符, 保持其在原字符串中的相对顺序, 然后组成的新的string就是subsequence, 别混淆了。 

最长回文子串(LPS)就是从给定的字符串中找出最长的一个子字符串, 这个子字符串从左往右读和从右往左读, 读出的结果是一样的。

例如:aba, a, abba均是回文子串。 在例如, S = "abcbcbb"的LPS 是“bcbcb”。

(方法一) bruteforce的办法: 最简单的办法就是价差字符串的每一个子字符串是否为palindrome or not。  我们可以运行三个loops, 外层两个loops 一个个的选择字符串所有可能的子字符串, 内层循环检查选的该子字符串是否是palindorme的。 

[cpp] view plain copy
  1. #include <string>  
  2.   
  3. #include <iostream>  
  4.   
  5. using namespace std;  
  6.   
  7. /*判断str[i..j]是否为回文串*/  
  8. bool isPalindrome(string str, int Start, int End) {  
  9.     while(str[Start++] == str[End--] && Start <= End) { }  
  10.     if(str[--Start] != str[++End]) {  
  11.         return false;  
  12.     } else {  
  13.         return true;  
  14.     }  
  15. }  
  16.   
  17. /*返回最长回文子串长度*/  
  18. string longestPald(string str) {  
  19.     int len = str.length();  
  20.     int longest = 1;  
  21.     if(len == 1) {  
  22.         return str.substr(0, 1);  
  23.     }  
  24.     int longestStart = 0;  
  25.     for(int i = 0; i<len; i++) {  
  26.         for(int j = i + 1; j < len; j++) {  
  27.             if(isPalindrome(str, i, j)) {  
  28.                 if(longest < j - i + 1) {  
  29.                     longestStart = i;  
  30.                     longest = j - i + 1;  
  31.                 }  
  32.                 //longest = (longest < j-i+1 ? j-i+1: longest);  
  33.             }  
  34.         }  
  35.     }  
  36.     cout << longest << endl;  
  37.     return str.substr(longestStart, longest);  
  38. }  
  39. int main() {  
  40.     string s = "forgeeksskeegfor";  
  41.   
  42.     cout << longestPald(s) << endl;  
  43.   
  44.     return 0;  
  45. }  
运行结果如下:



分析: 时间复杂度:    O(n^3)

           辅助空间复杂度: O(1)


(方法二) 动态规划的办法:  我们maintain一个boolean的table[n][n], 然后自底向上的方式填充这个table。 如果子字符串str[i, j]是palindrome的 我们就把table[i][j]记为true, 否则就记为false。 为了计算table[i][j], 我们需要首先检查table[i+1][j-1], 如果table[i+1][j-1]是true的, 并且str[i]与str[j]是相同的字符, 我们就记录table[i][j]为true。否则table[i][j]就为false。 更详细的分析见下面:

如果substring(i, j)是palindromic的, 那么substring(i+1, j-1)也是palindromic的。

table[i][j]是palindromic的, 那么几位true。 otherwise, 记为0。

当j - i 比较小的时候, 即为0或者为1, 那么我们很容易知道:

table[i][i] = true,  table[i][i+1] = (S[i] == S[j]), 这是base case。

 另外, 还有table[i][j] = table[i + 1] [j - 1] && S[i]==S[j]。

table[i][i]如下:


table[i][i+1]如下:


table[i][i+2], table[i][i+3]等等如下:


程序如下:

[cpp] view plain copy
  1. #include <string>  
  2. #include <cstring>  
  3. #include <iostream>  
  4.   
  5.   
  6. using namespace std;  
  7.   
  8.   
  9. string longestPalindromeDP(string s) {  
  10.   int n = s.length();  
  11.   int longestBegin = 0;  
  12.   int maxLen = 1;  
  13.   bool table[1000][1000];  
  14.   memset(table, 0, sizeof(table[0][0]) * 1000 * 1000);  
  15.   // for substring of length 1 are palindrome  
  16.   for (int i = 0; i < n; i++) {  
  17.     table[i][i] = true;  
  18.   }  
  19.   //check for substring of length 2  
  20.   for (int i = 0; i < n-1; i++) {  
  21.     if (s[i] == s[i+1]) {  
  22.       table[i][i+1] = true;  
  23.       longestBegin = i;  
  24.       maxLen = 2;  
  25.     }  
  26.   }  
  27.   // check for lengths greater than 2  
  28.   // len is the length of substring  
  29.   for (int len = 3; len <= n; len++) {  
  30.     // fix the starting index  
  31.     for (int i = 0; i < n-len+1; i++) {  
  32.       int j = i+len-1;  
  33.        // checking for sub-string from ith index to  
  34.        // jth index iff str[i+1] to str[j-1] is a  
  35.        // palindrome  
  36.       if (s[i] == s[j] && table[i+1][j-1]) {  
  37.         table[i][j] = true;  
  38.         /*if(len > maxlen) { // 总是成立, 所以可以去掉这个条件 
  39.             longestBegin = i; 
  40.             maxLen = len; 
  41.         } */  
  42.         longestBegin = i;  
  43.         maxLen = len;  
  44.       }  
  45.     }  
  46.   }  
  47.   return s.substr(longestBegin, maxLen);  
  48. }  
  49. int main() {  
  50.     string s = "forgeeksskeegfor";  
  51.   
  52.   
  53.     cout << longestPalindromeDP(s) << endl;  
  54.   
  55.   
  56.     return 0;  
  57. }  

运行结果如下:



分析: 时间复杂度: O(n^2)

           辅助空间复杂度: O(n^2) (其实还可以改进的, 使得改进后的空间复杂度为O(n))


(方法三)Manacher 算法, 时间复杂度为O(n)。 

虽然很屌, 太耗时间了。 有时间在研究。 you are not expected to think such a good algorithms。



(方法四): (利用最长公共子序列求解) LPS 就是str和str反转过来的LCS, 我们只需要找出这两个字符串的LCS即可。 这又归结为动态规划的内容了。 


(方法五): 观察到一个palindrome 是关于center 成镜像的。 例如 ab|ba, a b a, 注意palindrome为偶数个字符的时候,中心在两个字符之间, 当palindrome的字符个数为奇数的时候, 中心是在最中间的那个字符。 

对于一个具有N个字符的字符串, 共有2N - 1 个centers。 因为两个字符之间也可能是center,字符本身也可能是center, 所以是2N - 1。 所以我们只需要对每个中心进行expand的方式查找palindrome, 每个中心需要花费O(N), 所以这个方法的时间复杂度是O(N^2)。 

[java] view plain copy
  1. string expandAroundCenter(string s, int c1, int c2) {  
  2.   int l = c1, r = c2;  
  3.   int n = s.length();  
  4.   while (l >= 0 && r <= n-1 && s[l] == s[r]) {  
  5.     l--;  
  6.     r++;  
  7.   }  
  8.   return s.substr(l+1, r-l-1);  
  9. }  
  10.    
  11. string longestPalindromeSimple(string s) {  
  12.   int n = s.length();  
  13.   if (n == 0return "";  
  14.   string longest = s.substr(01);  // a single char itself is a palindrome  
  15.   for (int i = 0; i < n-1; i++) {  
  16.     string p1 = expandAroundCenter(s, i, i);  
  17.     if (p1.length() > longest.length())  
  18.       longest = p1;  
  19.    
  20.     string p2 = expandAroundCenter(s, i, i+1);  
  21.     if (p2.length() > longest.length())  
  22.       longest = p2;  
  23.   }  
  24.   return longest;  
  25. }