现在的位置: 首页 > 综合 > 正文

HDU3068-最长回文—O(n)时间求最长回文子串

2014年09月05日 ⁄ 综合 ⁄ 共 2535字 ⁄ 字号 评论关闭

最长回文

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4641    Accepted Submission(s): 1567

Problem Description
给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.
回文就是正反读都是一样的字符串,如aba, abba等

Input
输入有多组case,不超过120组,每组输入为一行小写英文字符a,b,c...y,z组成的字符串S
两组case之间由空行隔开(该空行不用处理)
字符串长度len <= 110000

Output
每一行一个整数x,对应一组case,表示该组case的字符串中所包含的最长回文长度.

Sample Input
aaaa abab

Sample Output
4
3

 

 

题意:给你一个字符串,求出这个字符串里面最长的回文子串(什么是回文串我就不说了,当大家都知道吧)。

 

思路:

方法一:最简单的思路,枚举子串的起点和终点,但是对这道题来说,毫无疑问,超时!

方法二:枚举回文串的中间位置,看刘汝佳的算法书,枚举代码如下:

#include<stdio.h>
#define N 110010
char s[N+1];
int main() {
    int i,j,len,max,x,y;
    while(scanf("%s",s)!=EOF){ 
        len=strlen(s);
        for(i=0,max=0;i<len;i++) {
            for(j=0;j<=i&&i+j<len;j++) {//回文串长度为奇数                
            if(s[i+j]!=s[i-j]) break;
                if(2*j+1>max){
                    max=2*j+1;
                    x=i-j;
                    y=i+j;
                }
            }
            for(j=0;j<=i&&i+j+1<len;j++) {//回文串长度为偶数               
            if(s[i]!=s[i+j+1]) break;
                if(2*j+2>max){
                    max=2*j+2;
                    x=i-j;
                    y=i+j+1;
                }
            }
        }
        printf("%d\n",max);
        /*for(i=x;i<=y;i++)
            printf("%c",s[i]);
        printf("\n");
        */
    }
    return 0;
}

很悲催,仍然超时!

方法三:思路仍然是枚举终点,看向两边能够拓展多长,但是避免了重复匹配(以下内容为粘贴)。

原文链接:http://www.cnblogs.com/wuyiqi/archive/2012/06/25/2561063.html

算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
    原串:    w aa bwsw f d
    新串:   # w # a # a # b # w # s # w # f # d #
辅助数组P:  1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
    这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度(包括‘#’)。如果这里不是特别清楚,可以自己拿出纸来画一画,自己体会体会。当然这里可能每个人写法不尽相同,不过我想大致思路应该是一样的吧。
    好,我们继续。现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。
    由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)
好,到这里,我们可以先贴一份代码了。

void pk()
{
    int i;
    int mx = 0;
    int id;
    for(i=1; i<n; i++)
    {
        if( mx > i )
            p[i] = MIN( p[2*id-i], mx-i );        
        else
            p[i] = 1;
        for(; str[i+p[i]] == str[i-p[i]]; p[i]++)
            ;
        if( p[i] + i > mx )
        {
            mx = p[i] + i;
            id = i;
        }
    }
}

    代码是不是很短啊,而且相当好写。很方便吧,还记得我上面说的这个算法避免了很多不必要的重复匹配吧。这是什么意思呢,其实这就是一句代码。
if( mx > i )
    p[i] = MIN( p[2*id-i], mx-i );
就是当前面比较的最远长度mx>i的时候,P[i]有一个最小值。这个算法的核心思想就在这里,为什么P数组满足这样一个性质呢?

代码如下:

#include<stdio.h>
#define N 110000
char s[2*N+10],str[2*N+10];
int p[2*N+10];

int min(int a,int b) {
    return a<b?a:b;
}
int main() {
    int i,j,id,max,len;
    while(scanf("%s",s)!=EOF) {
        
        len=strlen(s);
        for(i=0,str[0]='$',str[1]='#';i<len;i++) {
            str[i*2+2]=s[i];
            str[i*2+3]='#';
        }
        len=len*2+2;
        str[len]='\0';
        for(i=1,max=0;i<len;i++) {
            if(max>i)
                p[i]=min(p[id*2-i],p[id]+id-i);
            else 
                p[i]=1;
                
            for(;str[i+p[i]]==str[i-p[i]];p[i]++);//往两边拓展
            
            if(p[i]+i>max) {
                max=p[i]+i;
                id=i;
            } 
        }
        for(i=0,max=0;i<len;i++)
            if(p[i]>max)
                max=p[i];
        printf("%d\n",max-1);
    }
    return 0;
}
/*
waabwswfd
*/

 

 

抱歉!评论已关闭.