问题描述
给定一个字符串str,求该字符串的最长回文子序列的长度。
动态规划求解(并不一定能求出正确答案)
定义二维数组dp[i][j]代表下标为i到下标为j范围内的最长回文子序列的长度,当0<=i<=j<n时有意义,即起始位置要小于等于终止位置。而dp[i][j]的值是取决于dp[i+1][j-1]的值、str[i]和str[j]。
如果str[i]=str[j],那么dp[i][j]=dp[i+1][j-1]+2;
如果str[i]!=str[j],那么dp[i][j]=max(dp[i][j-1],dp[i+1][j]);即不相等的时候最长的子序列为[i,j-1]和[i+1,j]的最大值。
class Solution {
public int function(String str) {
int n = str.length();
int[][] dp = new int[n][n];
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
char c1 = str.charAt(i);
for (int j = i + 1; j < n; j++) {
char c2 = str.charAt(j);
if (c1 == c2) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
}
注意:
问题:用动态规划并不能得到所有的正确答案,如果使用测试用例dabcd,得到的答案是3,而实际答案应该是1,这是因为动态规划在计算[0,4]范围内的最长子序列的时候需要知道[1,3]范围内的子序列长度,而[1,3]范围的最长子序列长度为1,即dp[1][3]=1,而又判断str[0]和str[4]是相等的,所以dp[0][4]=dp[1][3]+2,就得到了错误的答案3。
manacher算法
动态规划虽然能够求得一部分的答案,但是总有一些答案是不正确的,而且动态规划的时间复杂度是O(N²)。而使用manacher算法不会出现这种错误,并且时间复杂度可以达到O(N)。
用一个数组ptr来存放以每一个元素为中心的回文串长度,R用来存放已经扩展的最右边界。由于该方法只适用于处理字符个数为奇数的字符串,所以给每一个字符之间加上一个字符’#’,这样不管原来的字符串是奇数串还是偶数串,处理之后的都是奇数串。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
c | a | b | a | d | a | b | a | d | d | c |
i’ | C | i | R |
当C为中点的时候,i在R内,由于i关于C的对称点i’已经知道臂长为2(算上i’自身),所以i的臂长也为2,此时下标为5、6、7这一段是不需要计算是否为回文序列的,因为i与i’关于C点对称都在C的臂长之内,i’点的序列段1、2、3是回文序列,所以i点在R内的部分能够确定就是回文序列,所以只需要继续计算R之外的元素是否可以继续关于i点扩展即可。
可以看出str[4]=str[8],所以i的臂长加一变为3,即ptr[i]=3,此时已经超过了右边界R,所以需要将R进行更新,并将中心点更新为C,并且更新最大回文子串的长度。
class Solution {
public int function(String s) {
StringBuilder str=new StringBuilder();
str.append("#");
int i,n;
for(i=0;i<s.length();i++){
str.append(s.charAt(i));
str.append("#");
}
str.toString();
n=str.length();
int[] ptr=new int[n]; //存放str[i]的臂长
int R=-1,C=-1,max=Integer.MIN_VALUE;//R是最右边界,C是中心对称点
for(i=0;i<n;i++){
ptr[i]=R>i?Math.min(ptr[2*C-i],R-i):1;//先得到i的最短不用验证的臂长
while(i+ptr[i]<n&&i-ptr[i]>-1){ //查看以i为中心的最长子串是否越过左边界-1和右边界n
if(str.charAt(i+ptr[i])==str.charAt(i-ptr[i])){ //进一步验证i的臂长是否要增长
ptr[i]++;
}else{
break;
}
}
if(i+ptr[i]>R){
R=i+ptr[i]; //更新右边界R
C=i; //更新中心点C
}
max=Math.max(max,ptr[i]);
}
return max-1;
}
}