动态规划并不一定能求字符串的最大回文子序列长度->manacher算法

问题描述

给定一个字符串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用来存放已经扩展的最右边界。由于该方法只适用于处理字符个数为奇数的字符串,所以给每一个字符之间加上一个字符’#’,这样不管原来的字符串是奇数串还是偶数串,处理之后的都是奇数串。

012345678910
cabadabaddc
i’CiR

当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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值