Leetcode---Longest Palindromic Substring

本文详细介绍了四种解决最长回文子串问题的方法:暴力解法、枚举中心法、动态规划及Manacher算法。重点讲解Manacher算法的原理与实现过程。

最长回文字串(Longest Palindromic Substring)

题目:Given a string S, find the longest palindromic substring in S. You may assume that the             maximum length of S is 1000, and there exists one unique longest palindromic substring.

题目连接https://leetcode.com/problems/longest-palindromic-substring/

    大意是给定一个字符串S,求S中最长的回文字串,回文字符串是指从坐到右和从右到左都是一样的字符串,例如:“西湖回游鱼游回湖西”、“ABDCDBA”、“abccba”等等诸如此类的字符串,单个字符的串,也是回文串。

思路1:暴力解法,枚举S的所有字串,判断每个字符串是否是回文串,返回最长的串,当字符串很长的时候,枚举也只能想想了,更别说还要判断是否是回文。
思路2:枚举中心法,我们感兴趣的只是串中的回文字串,因为回文串的性质,那么我们可以枚举串中每一个字符,来检查以它为中心的串是不是回文串,并选取其中最长的返回。
     
     例如: ABDCDBA  
    1.以A为中心,A的左边没有字符,所以只能是A当作一个回文。
2.以B为中心,那么比较B的左边和右边字符是否相等。相等,是回文。否则,不是。
3.以此类推,直到所有的字符作为中心判断完。
假设以i为中心,j为i左右的长度,且满足条件1:(i-j)>=0 && (i+j)<S.size(); j从0开始,到不能满足条件1结            束,分别判断,判断完成以i为中心后,以i+1为中心再进行类似判断。当然还有一个问题,就是回文串的长度有          可能是奇数,也可能是偶数。例如ABA、ABBA都是回文。这个时候只需要对奇数还是偶数进行分别判断:
               奇数时:判断S[i-j] == S[i+j],j满足(i-j)>=0 && (i+j)<S.size()
       偶数使:判断S[i-j] == S[i+j+1],j满足(i-j)>=0 && (i+j+1)<S.size()
CODE: 
             
string LongestPalindrome( string s)
{
    int start=0,maxLen = 1,tlen = 1,ti=0;
    int size = s.size();
    
    if( size <= 1 )
        return s;
    
    
    // 奇数
    for( int i = 0; i < size; i++)
    {
        for( int j = 0; (i-j>=0) && (i+j
   
    = maxlen )
        {
            maxlen = tlen;
            start = i-j;
        }
        
        
          // 偶数
        for( int j = 0; (i-j>=0) && (i+j+1 <= n); ++j )
        {
            if( s[i-j] != s[i+j+1])
                break;
            tlen = 2*j+2;
            ti = i-j;
        }
        
        if(tlen > maxlen )
        {
            maxlen = tlen;
            start = ti;
        }
    }
    
    return s.substr(start,maxlen);


}
   

思路3:先看下面的例子:
     A  AABABBAABCBABC ....
      可以看出什么? A按规定是一个回文,要判断BAB是不是回文,那么判断B==B,且A是不是回文。判断BAAB,则判断B==B? 且AA是不是回文。以此类推,可以总结    到:
1.单个字符是回文。
2.两个字符相同是回文,否则不是。
3.长度大于等于3的串,满足s[0]==s[s.size-1] && s.substr(1,s.size-2)是不是回文。满足则是,否则不是。
   更一般化来讲:
对于以i为起点,j为长度的string是不是回文串,只需要判断,s[i]==s[s+j-1]且s[i+1 to i+j-2]之间的串是不是回文串,这样问题规模就减小了,对于规模    大的问题转换到小规模问题求解,那么很容易想到的就是动态规划。
    DP[i][i] = true;    i<s.size();
DP[i][i+1] = true;  if( s[i]==s[i+1] && i<s.size()-1)
DP[i][j] = true;    if( s[i]==s[j] && DP[i+1][j-1] && i<size & j < size)
   按照这个思路很自然可以得出代码:
   CODE
   
string longestPalindrome(string s) {
   
        const int size = s.size();
        if( size == 1)
            return s;
            
        bool dp[1000][1000] ;
        
        for( int i = 0; i < 1000; i++)
            memset(dp[i],false,1000);
            
        int maxlen =1;
        int start = 0;
        
        for( int i = 0; i < 1000; i++)
        {
            dp[i][i]= true;
            if( i<(size-1) && (s[i] == s[i+1]))
            {
                dp[i][i+1]=true;
                start = i;
                maxlen = 2;
            }
        }
        
        for( int len = 3; len <= size; len++)
        {
            for(int i = 0; i < size-len+1; i++)
            {
                int j = i+len-1;
                if( dp[i+1][j-1] && (s.at(i)==s.at(j)))
                {
                    dp[i][j] = true;
                    start = i;
                    maxlen = len;
                }
            }
        }
        
        if( maxlen >= 2)
        {
            return s.substr(start,maxlen);
        }
        
        return NULL;
    }

  抛开暴力枚举的方法,因为效率太低了,中心枚举和动态规划的时间复杂度都是O(n^2),还有没有更高效的方法呢?当然有,这就是传说中的Manacher算法,这个方法需要点想象力啊。

思路4:Manacher算法,这就是个传说。 Manacher算法思想是来自上述“中心扩展法”,在“中心法”中要考虑回文 串是奇数还是偶数,有没办法统一处理呢?有,这就是Manacher算法。
    该算法首先将字符串的每个字符之间加入一个特殊字符,并且在字符串开头加入一个标志来更好处理越界和编码问题。例如:s=“abba”编码后是:s=“$#a#b#b#a#”。
   然后,用一个数组p来记录每个字符是s[i]为中心的的回文串向左或向右的长度,则原字符串s的回文长度为max(p[i]-1)。因为对于一个回文字符串s加入特殊字符后,该回文串加上特殊字符后依然会是一个回文串,且编码后的回文串的长度为2*s.size+1。那么数组记录的最大值为s.size+1,比原回文的长度多1。故求取p[]的最大值减1就是原字符串中最大的回文串。
   现在最重要的问题就是怎么求解p[],这似乎又回到原来开始的问题了,非也。因为加了特殊字符,会出现一些有用的结论。
   假设现在求p[i]的值,则p[0]...p[i-1]的值已经求出,maxlen为目前已经求出的回文串延伸到右边的最大长度,id为p[0]到p[i-1]最大回文串的中心,j为i关于id的对称点,即j=2*id-i。那么:
   1.如果i不在maxlen范围内,即不在任何回文串中,则p[i]=1(本身为回文)。接着判断p[i+j]==p[i-j],为真则++p[i],否则2;
   2.p[i]>=min(p[2id-i],maxlen-i]。
   证明:
情况1:
         

情况2:
情况3:
int Manacher(string s)
{
	int len = s.size();
	int maxLen = 0;
	int id;
	int mx = 0;

	for (int i = 1; i < len; i++)
	{
		if (i < maxLen)
			p[i] = min(p[2 * id - i], maxLen - i);
		else
			p[i] = 1;

		while (s_new[i - p[i]] == s_new[i + p[i]])
			p[i]++;


		if (maxLen < i + p[i])
		{
			id = i;
			maxLen = i + p[i];
		}

		mx = max(mx, p[i] - 1);
	}

	return mx;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值