leetCode.005 Longest Palindromic Substring
(转载)LeetCode上一道求回文字符串的问题,通常容易想到的是暴力和动态规划的方法,时间复杂度为O(n^2),后来看到有一种更巧妙的算法,Manacher’s algorithm,时间复杂度为O(n)。参考大神的博客,记下来自己的理解,方便日后查阅。
1.预处理
首先,由于通常情况下,对于奇数长度和偶数长度的不同字符串,我们需要分别处理。这里,通过巧妙的预处理,在每一个字符两边增加一个特殊字符,例如:#a#b#a#a#b#a#a#b#,这样就可以枚举到每一个中心了,就是说把两种情况都统一起来。算法的思想是利用前面已经求得的回文子串的信息,来求以当前字符中心的回文子串。
2.空间换时间
然后,我们需要用一个数组 P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i]),比如S和P的对应关系:
- S # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #
- P 1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1
- (p.s. 可以看出,P[i]-1正好是原字符串中回文串的总长度)
那么怎么计算P[i]呢?该算法增加两个辅助变量(其实一个就够了,两个更清晰)id和mx,
其中id表示最大回文子串中心的位置,mx则为id+P[id],也就是最大回文子串的边界。
注意:在算法中,id和mx是一直更新的,其表示,以id为起点,半径为mx势力范围内能够罩住的区间,即[id - mx,id + mx]。
3.算法逻辑分析
假设现在我们要求P[i],i关于id的对称点j是否有信息可以利用呢?主要看id为中心的回文串半径与P[j]的关系
总的来说,有三种情况
a.完全覆盖,不包括边界
此时可以完全利用i的对称点j的已有信息,即P[i]=P[j];此时,不会更新id和mx值,即可以直接处理下一个字符。
b.恰好覆盖,到达边界
此时,就需要就在边界的下一个开始继续比较下去
c.未能覆盖
此时,p[i] = mx - i 且 不会更新id和mx值,即可以直接处理下一个字符。
那么P[i]=j+P[j]-i ;因为如下图,1、13不在pj里面,就是说s[1]!=s[13],而在p4里面有s[1]=s[7],所以s[13]!=s[7],所以不能形成更长的回文串。
1 char* s_Trans_t(char* s) 2 { 3 char* t; 4 t=(char*)malloc(sizeof(char)*2500); 5 t[0]='#'; 6 for(int i=0;s[i]!='\0';i++) 7 { 8 t[2*i+1]=s[i]; 9 t[2*i+2]='#'; 10 } 11 return t; 12 } 13 char* longestPalindrome(char* s) { 14 char *t; 15 char* ans; 16 t=(char*)malloc(sizeof(char)*2500); 17 ans=(char*)malloc(sizeof(char)*2500); 18 t=s_Trans_t(s); 19 size_t lens; 20 lens=strlen(t); 21 int p[2500],mx=0,id=0,max_id=0,min; 22 memset(p,0,sizeof(p)); 23 for(int i=0;t[i]!='\0';i++) 24 { 25 min=p[2*id-i]>mx-i?mx-i:p[2*id-i]; 26 p[i]=mx>i?min:1; 27 while(t[i-p[i]] == t[i+p[i]] &&(i-p[i]>=0)&&(i+p[i]<lens)) 28 p[i]++; 29 if(i+p[i]>mx) 30 { 31 mx=i+p[i]; 32 id=i; 33 } 34 if(p[i]>p[max_id]) 35 max_id=i; 36 } 37 int j=0; 38 for(int i=max_id-p[max_id]+1;i<max_id+p[max_id]-1;i++) 39 { 40 if(t[i]!='#') 41 ans[j++]=t[i]; 42 } 43 ans[j]='\0'; 44 return ans; 45 }

浙公网安备 33010602011771号