最长回文子串和回文子序列

Leetcode5. Longest Palindromic Substring最长回文字串

解决最长回文字串的方法有很多种,在这边主要介绍Manacher方法(时间复杂度O(n),空间复杂度O(n)):感觉想出这个方法的人真的是很厉害啊,像我这样的光理解都要理解很久哈~。

首先,回文子串总会遇到是奇串还是偶串的问题,Manacher解决这个问题的方法就是在每个字串中间插入一个特殊字符如”#”或者是其他的,只要原字符中一定不会出现就行了。举例说明,原字符串是bababac就会变成#b#a#b#a#b#q#c#,也可以在最前面加上一个$符,这样就可以不用一判断是否越界了,因为$和谁都不想等,所以到$的时候就一定会停止,从而不会越界。

其实算法的主要思想就是借助回文子串的特性,使用一个数组来记录以每个字符到以他为中心的最长回文子串最右字符的距离,使用变量maxRight来记录当前遍历的字符中能到达的最右位置下标最大的(maxRight在这里的意义其实就是看当前遍历到的字符是否是以其他字符为中心的回文子串的一部分),maxCentre记录得到maxRight的中心(其实只有一个maxCentre变量也可以),然后就是算法的重点,如何去确定以当前字符文中心的最长回文字串的长度。

现在设下标为i,分成二大种情况:

1)    第一种情况是s[i] > maxRight,就是i这个下标的字符没有成为任何回文子串的一部分,那它的最长回文子串的长度只能通过普通的方法自己去找;

2)    第二种情况就是s[i] < maxRight,下标为i的这个字字符成为了其他的回文子串(也就是以maxCentre为中心的最长回文子串)的一部分了,我们知道回文子串一定是中心对称的,那说明在对称点的另一部分肯定有一个字符串(假设他的下标是k=maxCentre -(i-maxcentre) )和我们的当前字符在这个回文子串的范围内的周围情况完全相同,所以现在我们考虑如果以下标i字符为中心的最长回文子串以下标为k的这个字符的最长回文子串相同,那么i字符能到达的最右边界是否超过了以maxCentre为中心的回文子串(也就是是否超过了maxRight),如果没有超出,那就很好正好是我们想看到的情况,下标为k的字符和下标为i的字符是完全对称的两个字符,i的最长回文字串一定和k的最长回文子串相同。如果超出了maxRight,那么在maxRight里面的部分一定是对称的,就从超出maxRight的部分再去用普通的方法去判断是否继续对称就行了。

这边来举个例子,还拿上面所说的$#b#a#b#a#b#q#c#来举例子,在前面加了个$,从头开始,$和第一个#到他们的最长回文字串最右边的距离为0(之后省略说成距离),字符b左右两个#对称所以是1……

下标    0 1 2 3 4 5 6 7 8 9.......

字符串$ # b # a # b # a # b # q # c #

距离    0 0 1 0 3 0 5 0 5 0 3 0 1 0 1 0

到了第一个a的时候他的距离是3,此时maxCentre = 4,maxRight=7,那么对于下面的#,他的下标为5<7,则他直接去参照下标 4-(5-4)=3的值,发现下标为3 的”#“的距离是0,下标5的”#”的距离就也是0。对于下标为6的”b”,6<7那么去看他的对称点下标为4-(6-4)=2的“b“的距离为1,6+1>=7的,则下标6的距离也一定是>=1的,那么从下标8的位置继续去看是否对称,可以看到下标6的距离为5,此时要更新maxCentre = 6,maxRight = 11。如果这边还不明白可以自己据需把下面的推完。

public class Solution {
    public String longestPalindrome(String s) {
        if(s.length() == 0) return s;
        char[] ss = preprocess(s);
        int[] p = new int[ss.length];//p用来记录以当前下标为中心的最长字串从中心点到右边界的长度。
        p[0] = 0;p[1] = 0;
        int maxCentre = 1;
        int maxRight = 1;//记录maxCentre 能到达的最右位置
        for(int i = 2; i< ss.length ;i++){
            if(i > maxRight) p[i] = getPalindrome(ss, 0,i-1,i+1);
            else{
                //对称点
               int symmetry = maxCentre - (i - maxCentre); 
               //对称点的最长回文包含在了maxCentre的最长回文字串中。
               if(maxRight - i > p[symmetry]) 
                    p[i] = p[symmetry];
               //对称点的最长回文没有完全包含在maxCentre的最长回文字串中。
               else
                    p[i] =  getPalindrome(ss, maxRight - i,i-(maxRight-i) - 1,maxRight + 1);
            }
            
            // System.out.println("i=" + i + " maxRight = " + maxRight + "  p[i]" + p[i]);
            if(p[i] + i > maxRight){
                maxCentre = i;
                maxRight = p[i] + i;
            }
        }
        int maxL = Integer.MIN_VALUE;
        int maxIndex = 0;
        for(int i = 0; i < p.length; i++){
            if(p[i] > maxL){
                maxL = p[i];
                maxIndex = i;
            }
        }
        return s.substring((maxIndex-maxL)/2,(maxIndex+maxL)/2);
    }
    
    public int getPalindrome(char[] ss, int length,int left,int right){
        //首位是$,所以left一定不会越界,
        while(right < ss.length && ss[left] == ss[right]){
            left--;right++;
            length ++;
        }
        return length;
    }
    
    public char[] preprocess(String s){
         char[] ss = s.toCharArray();
         char[] res = new char[ss.length * 2 + 2];
         res[0] = '$';
         res[1] = '#';
         for(int i = 0; i < ss.length; i++){
             int index = i * 2 + 2; 
             res[index] = ss[i];
             res[index + 1] = '#';
         }
         return res;
     }
}

Leetcode516 LongestPalindromic Subsequence最长回文子序列

求最长回文子序列可以看成字符串a和他的逆序字符串b的最长公共子序列问题,最长公共子序列可以参照  http://blog.youkuaiyun.com/weixin_37974811/article/details/69801412

然后也不多说直接上代码

public class Solution {
    public int longestPalindromeSubseq(String s) {
        char[] ss = s.toCharArray();
        if(ss.length == 0) return 0;
        char[] ress = Arrays.copyOf(ss,ss.length);
        reverse(ress);
        int[][] f = new int[ss.length][ss.length];
        for(int i = 0; i < ss.length; i++){
            for(int j = 0; j < ss.length; j++){
                if(ss[i] == ress[j]) {
                    if(i -1 >= 0 && j - 1 >= 0) 
                        f[i][j] = f[i-1][j-1]+1;
                    else 
                        f[i][j] = 1;
                }
                else{
                    f[i][j] = Math.max(i-1>=0? f[i-1][j]:0,j-1>=0?f[i][j-1]:0);
                }
            }
        }
        return f[ss.length-1][ss.length-1];
    }
    public void reverse(char[] ss){
        int i = 0, j = ss.length-1;
        while(i < j){
            char t = ss[i];
            ss[i] = ss[j];
            ss[j] = t;
            i++;j--;
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值