法一:简单方法
两层循环对字符串中所有的子串进行定位,进而判断是否是回文子串,复杂度O(n^3)。
方法简单,易想到,但暴力,易超时。
for(i=0;i<m;i++)
{
for(j=i;j<m;j++)
{
int ok=1;
for(k=i;k<=i+(j-i)/2;k++) // i,j定位,使用k扫描匹配
if(s[k]!=s[i+j-k]) ok=0;
if(ok && j-i+1>max) {max=j-i+1;x=i;y=j;} //最长更新
}
}
法二:动态规划
还是使用两层循环对子串定位,b[i][j]表示以i,j 为边界的子串是否是回文。
不同的是使用动态规划,通过子结构的递推关系来求取最优解,
b[i][j] = (s[i]==s[j])&&isPa(b,i+1,j-1) // i位置与j位置相同,则判断其去掉两边后的子串
出口: i==j b[i][j]=1
但是时间复杂度O(n^2),还是易超时
//b[m][m]中每个元素初始化为0
for(i = 0; i < m; i++)
for(j = i + 1; j < m; j++)
if(isPa(b, i, j) == 1 && j - i > mj - mi)
mj = j; mi = i;
//isPa函数定义如下
isPa(int b[] , int i, int j)
if(b[i][j] != 0)
return b[i][j];
if(j <= i)
b[i][j] = 1;
else
b[i][j] = (b[i] == b[j]) && isPa(b, i + 1, j - 1);
return b[i][j];
法三:manacher算法
下面介绍manacher,复杂度O(n)。
由于回文分为偶回文和奇回文,为了统一处理,使用一个技巧,具体做法是:在字符串首尾,及各字符间各插入一个不相关字符,如#。这样保证了回文串都是奇数。
原串:abcdcbaf
下标 id |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
拷贝串 | # | a | # | b | # | c | # | d | # | c | # | b | # | a | # | f | # |
数组p | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 8 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 2 |
容易证明,p[id] - 1 就是回文子串在原串中的长度,如上例中,最长子串为 abcdcba ,长度为7,p[8] - 1 = 7。
算法关键在于如何求出p。
变量 MaxId 记录扫描串时所到达的最右边的下标;
id 记录扫描到最右边串时当前子串的中心;
这个是leetcode里面Longest Palindromic Substring的回答:
#define min(a,b) (a) > (b)? (b): (a)
char* longestPalindrome(char* s) {
char auxStr[2001];
int i, p[2001] = {0}, MaxId = 0, id = 0, leng, j, start = 0;
//拷贝数组
auxStr[0] = '^';
for (i = 0; s[i] != '\0'; ++i){
auxStr[i+1+i+1] = s[i];
auxStr[i+1+i] = '#';
}
auxStr[i+1+i] = '#';
auxStr[i+1+i+1] = '\0';
for (i = 1; auxStr[i] != '\0'; i++){ //对每个元素扫描 i
if (MaxId > i) //MaxId回文串最右边界
p[i] = min(MaxId - i, p[id + id - i]);
else
p[i] = 1;
while (auxStr[i + p[i]] == auxStr[i - p[i]])
++p[i];
if (MaxId < i + p[i]){
MaxId = i + p[i];
id = i;
}
if (p[start] < p[i])
start = i;
}
i = (start - p[start])/2;
leng = p[start] - 1;
for (j = 0; j < leng; ++i){
auxStr[j++] = s[i];
}
auxStr[j] = '\0';
return auxStr;
}
Longest Palindromic Substring