最长回文字符串问题

回文字符串是笔试中常考的题,下面给出回文字符串的一个经典题型:

求出一个字符串当中的最长回文字符串长度

1.最基本的解法

对于这个问题,我们考虑到回文字符串的性质,即回文字符串正读,反读相同,左右对称,这样一来,最基本的方法就是得到所求字符串的所有子串,并判断它们是否是左右对称,方法如下:

bool isAym(char *cbegin, char *cend)
{
    if(cbegin == NULL || cend ==NULL || cbegin > cend)
    {
        return false;
    }
    while(cbegin<cend)
    {
        if(*cbegin!=*cend)
        {
            return false;
        }
        cbegin++;
        cend--;
    }
    return true;
}
int getMaxSym(char * str)
{
    if(str == NULL)
        return 0;
    int maxlength = 0, strlength = 0;
    char *pFirst = str;
    char *strEnd = str + strlen(str);
    while(pFirst < strEnd)
    {
        char *pLast = strEnd;
        while(pLast > pFirst)
        {
            if(isAym(pFirst, pLast))
            {
                strlength = pLast - pFirst + 1;
                if(strlength > maxlength)
                {
                    maxlength = strlength;
                }
            }
            pLast --;
        }
        pFirst ++;
    }
    return maxlength;
}

既然是最基本的方法,那么不可避免的就是他的效率问题,时间复杂度为O(n^3),显然效率不太高

2.改进方法

对于上面的那种方法,之所以会效率低,很重要的一点就是它对字符串的判断是从外向里的,即先判断"abba",在判断其中的"bb",而当范围较大的字符串进行过的判断,在范围小的字符串中又进行了判断,这样一来就进行了大量重复的判断

而可以采取的改进措施就是将对字符串的判断改成从里向外,也就是先判断子字符串(如dd)是不是对称的。如果它(dd)不是对称的,那么向该子字符串两端各延长一个字符得到的字符串肯定不是对称的。如果它(dd)对称,那么只需要判断它(dd)两端延长的一个字符是不是相等的,如果相等,则延长后的字符串是对称的。

方法如下:

int getMaxSym2(char * str)
{
    if(str == NULL)
        return 0;
    int maxlength = 0;
    char *ptag = str;
    while(*ptag !='\0')
    {
        //奇数子字符串
         char *left = ptag - 1;
        char *right = ptag + 1;
        int oddlenght = 1;
        while(left >= str && *right != '\0' && *left == *right)
        {
            left--;
            right++;
            oddlenght += 2;
        }
        if(oddlenght > maxlength)
        {
            maxlength = oddlenght;
        }
        //偶数子字符串
         left = ptag;
        right = ptag + 1;
        int evenlength = 0;
        while(left >= str && *right != '\0' && *left == *right)
        {
            left--;
            right++;
            evenlength += 2;
        }
        if(evenlength > maxlength)
        {
            maxlength = evenlength;
        }

        ptag++;
    }
    return maxlength;
}
由于子字符串的长度可能是奇数也可能是偶数。长度是奇数的字符串是从只有一个字符的中心向两端延长出来,而长度为偶数的字符串是从一个有两个字符的中心向两端延长出来。因此程序中要把这两种情况都考虑进去。 

由于总共有O(n)个字符,每个字符可能延长O(n)次,每次延长时只需要O(1)就能判断出是不是对称的,因此整个函数的时间效率O(n^2) 
上述方法称为朴素算法,关于字符串的题目常用的算法有KMP、后缀数组、AC自动机,这道题目利用扩展KMP可以解答,其时间复杂度也很快。但是,这里介绍一个专门针对回文子串的算法,其时间复杂度为O(n),这就是manacher算法。

3.manacher算法

这里我们利用一个辅助数组,用来记录以字符串中的每个字符为中心的最长回文字符子串长度,当然为了避免上面的方法中遇到的j奇偶字符串的问题带来的麻烦,同时也为了避免在算法中频繁考虑数组的越界问题,我们对原字符串进行变形(即原字符串为"abaab",这里我们将其变为"$#a#b#a#a#b#")

方法如下:

char* GetNewStr(char* str)
{
	char* dst=new char[2*strlen(str)+3];
	int i=0;
	dst[i++]='$';
	while(*str)
	{
		if(i%2==0)
		{
			dst[i++]=*str;
			str++;
		}
		else
		{
			dst[i++]='#';
		}
	}
	dst[i++]='#';
	dst[i]='\0';

	return dst;
}


int GetMaxSubBackStr(char* str)
{
	if(str==NULL)
		return 0;

	char* dst=GetNewStr(str);
	int* len=new int[strlen(dst)];
	len[0]=0;
	int i=1;
	int maxlen=0;
	int idx=0;

	for(;i<strlen(dst);i++)
	{
		if(i<maxlen)
		{
			len[i]=len[2*idx-i]>maxlen-i?maxlen-i:len[2*idx-i];
		}
		else
		{
			len[i]=1;
			maxlen=len[i]+i;
			idx=i;
		}

		while(i-len[i]>=0&&i+len[i]<strlen(dst)&&dst[i-len[i]]==dst[i+len[i]])
		{
			len[i]++;
		}

	}

	maxlen=0;
	for(i=0;i<strlen(dst);i++)
	{
		if(maxlen<len[i])
			maxlen=len[i];
	}

	return maxlen-1;
}

这里进行了一些优化:当我们求某一个字符(位置为i)的对应最长回文字符串长度的时候,可以考虑它是否在前面求过的某个字符(位置为idx)对应的最长回文字符串当中,若在这当中,则可以利用这个条件,求出关于idx与i对称的位置j,看其边界是否超过idx对应的边界,若超过,则len[i]的值为maxlen-i,否则为对称位置的len[j]

参考链接:http://blog.csdn.net/yzl_rex/article/details/7908259

                    http://www.cnblogs.com/houkai/p/3371807.html





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值