对称子字符串的最大长度
题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。
分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。
引子:判断字符串是否对称
要判断一个字符串是不是对称的,不是一件很难的事情。我们可以先得到字符串首尾两个字符,判断是不是相等。如果不相等,那该字符串肯定不是对称的。否则我们接着判断里面的两个字符是不是相等,以此类推。基于这个思路,我们不难写出如下代码:
1 //////////////////////////////////////////////////////////////// 2 // Whether a string between pBegin and pEnd is symmetrical? 3 //////////////////////////////////////////////////////////////// 4 bool IsSymmetrical(char* pBegin, char* pEnd) 5 { 6 if(pBegin == NULL || pEnd == NULL || pBegin > pEnd) 7 return false; 8 9 while(pBegin < pEnd) 10 { 11 if(*pBegin != *pEnd) 12 return false; 13 14 pBegin++; 15 pEnd --; 16 } 17 18 return true; 19 }
要判断一个字符串pString是不是对称的,我们只需要调用IsSymmetrical(pString, &pString[strlen(pString) – 1])就可以了。
解法一:O(n3)的算法
现在我们试着来得到对称子字符串的最大长度。最直观的做法就是得到输入字符串的所有子字符串,并逐个判断是不是对称的。如果一个子字符串是对称的,我们就得到它的长度。这样经过比较,就能得到最长的对称子字符串的长度了。于是,我们可以写出如下代码:
1 //////////////////////////////////////////////////////////////// 2 // Get the longest length of its all symmetrical substrings 3 // Time needed is O(T^3) 4 //////////////////////////////////////////////////////////////// 5 int GetLongestSymmetricalLength_1(char* pString) 6 { 7 if(pString == NULL) 8 return 0; 9 10 int symmeticalLength = 1; 11 12 char* pFirst = pString; 13 int length = strlen(pString); 14 while(pFirst < &pString[length - 1]) 15 { 16 char* pLast = pFirst + 1; 17 while(pLast <= &pString[length - 1]) 18 { 19 if(IsSymmetrical(pFirst, pLast)) 20 { 21 int newLength = pLast - pFirst + 1; 22 if(newLength > symmeticalLength) 23 symmeticalLength = newLength; 24 } 25 26 pLast++; 27 } 28 29 pFirst++; 30 } 31 32 return symmeticalLength; 33 }
我们来分析一下上述方法的时间效率。由于我们需要两重while循环,每重循环需要O(n)的时间。另外,我们在循环中调用了IsSymmetrical,每次调用也需要O(n)的时间。因此整个函数的时间效率是O(n3)。
通常O(n3)不会是一个高效的算法。如果我们仔细分析上述方法的比较过程,我们就能发现其中有很多重复的比较。假设我们需要判断一个子字符串具有aAa的形式(A是aAa的子字符串,可能含有多个字符)。我们先把pFirst指向最前面的字符a,把pLast指向最后面的字符a,由于两个字符相同,我们在IsSymtical函数内部向后移动pFirst,向前移动pLast,以判断A是不是对称的。接下来若干步骤之后,由于A也是输入字符串的一个子字符串,我们需要再一次判断它是不是对称的。也就是说,我们重复多次地在判断A是不是对称的。
造成上述重复比较的根源在于IsSymmetrical的比较是从外向里进行的。在判断aAa是不是对称的时候,我们不知道A是不是对称的,因此需要花费O(n)的时间来判断。下次我们判断A是不是对称的时候,我们仍然需要O(n)的时间。
解法二:O(n2)的算法
如果我们换一种思路,我们从里向外来判断。也就是我们先判断子字符串A是不是对称的。如果A不是对称的,那么向该子字符串两端各延长一个字符得到的字符串肯定不是对称的。如果A对称,那么我们只需要判断A两端延长的一个字符是不是相等的,如果相等,则延长后的字符串是对称的。因此在知道A是否对称之后,只需要O(1)的时间就能知道aAa是不是对称的。
我们可以根据从里向外比较的思路写出如下代码:
1 //////////////////////////////////////////////////////////////// 2 // Get the longest length of its all symmetrical substrings 3 // Time needed is O(T^2) 4 //////////////////////////////////////////////////////////////// 5 int GetLongestSymmetricalLength_2(char* pString) 6 { 7 if(pString == NULL) 8 return 0; 9 10 int symmeticalLength = 1; 11 12 char* pChar = pString; 13 while(*pChar != '\0') 14 { 15 // Substrings with odd length 16 char* pFirst = pChar - 1; 17 char* pLast = pChar + 1; 18 while(pFirst >= pString && *pLast != '\0' && *pFirst == *pLast) 19 { 20 pFirst--; 21 pLast++; 22 } 23 24 int newLength = pLast - pFirst - 1; 25 if(newLength > symmeticalLength) 26 symmeticalLength = newLength; 27 28 // Substrings with even length 29 pFirst = pChar; 30 pLast = pChar + 1; 31 while(pFirst >= pString && *pLast != '\0' && *pFirst == *pLast) 32 { 33 pFirst--; 34 pLast++; 35 } 36 37 newLength = pLast - pFirst - 1; 38 if(newLength > symmeticalLength) 39 symmeticalLength = newLength; 40 41 pChar++; 42 } 43 44 return symmeticalLength; 45 }
回帖中指出了原文的问题:
当char *pChar =pString
由于子字符串的长度可能是奇数也可能是偶数。长度是奇数的字符串是从只有一个字符的中心向两端延长出来,而长度为偶数的字符串是从一个有两个字符的中心向两端延长出来。因此我们的代码要把这种情况都考虑进去。
在上述代码中,我们从字符串的每个字符串两端开始延长,如果当前的子字符串是对称的,再判断延长之后的字符串是不是对称的。由于总共有O(n)个字符,每个字符可能延长O(n)次,每次延长时只需要O(1)就能判断出是不是对称的,因此整个函数的时间效率是O(n2)。
以上转自何海涛博客
Manacher算法--O(n)回文子串算法
如何在O(n)时间内处理字符串以每个位置为中心的最长回文。这里转载一个Manacher算法的论文翻译。
原文地址:
http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/
其实原文说得是比较清楚的,只是英文的,我这里写一份中文的吧。
首先:大家都知道什么叫回文串吧,这个算法要解决的就是一个字符串中最长的回文子串有多长。这个算法可以在O(n)的时间复杂度内既线性时间复杂度的情况下,求出以每个字符为中心的最长回文有多长,
这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度
回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组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开始的)
好,到这里,我们可以先贴一份代码了。
1 void pk() 2 { 3 int i; 4 int mx = 0; 5 int id; 6 for(i=1; i<n; i++) 7 { 8 if( mx > i ) 9 p[i] = MIN( p[2*id-i], mx-i ); 10 else 11 p[i] = 1; 12 for(; str[i+p[i]] == str[i-p[i]]; p[i]++) 13 ; 14 if( p[i] + i > mx ) 15 { 16 mx = p[i] + i; 17 id = i; 18 } 19 } 20 } 21
代码是不是很短啊,而且相当好写。很方便吧,还记得我上面说的这个算法避免了很多不必要的重复匹配吧。这是什么意思呢,其实这就是一句代码。
if( mx > i ) p[i] = MIN( p[2*id-i], mx-i );
就是当前面比较的最远长度mx>i的时候,P[i]有一个最小值。这个算法的核心思想就在这里,为什么P数组满足这样一个性质呢?
(下面的部分为图片形式)
看完这个算法,你有可能会觉得这种算法在哪会用到呢?其实回文串后缀数组也可以做。只是复杂度是O(n log n)的,而且一般情况下也不会刻意去卡一个log n的算法。可正好hdu就有这么一题,你用后缀数组写怎么都得T(当然应该是我写得太烂了)。不信的话大家也可以去试试这题。 http://acm.hdu.edu.cn/showproblem.php?pid=3068
1 #include<iostream> 2 using namespace std; 3 4 const int N=300010; 5 int n, p[N]; 6 char s[N], str[N]; 7 8 #define _min(x, y) ((x)<(y)?(x):(y)) 9 10 void kp() 11 { 12 int i; 13 int mx = 0; 14 int id; 15 for(i=n; str[i]!=0; i++) 16 str[i] = 0; //没有这一句有问题。。就过不了ural1297,比如数据:ababa aba 17 for(i=1; i<n; i++) 18 { 19 if( mx > i ) 20 p[i] = _min( p[2*id-i], p[id]+id-i ); 21 else 22 p[i] = 1; 23 for(; str[i+p[i]] == str[i-p[i]]; p[i]++) 24 ; 25 if( p[i] + i > mx ) 26 { 27 mx = p[i] + i; 28 id = i; 29 } 30 } 31 } 32 33 void init() 34 { 35 int i, j, k; 36 str[0] = '$'; 37 str[1] = '#'; 38 for(i=0; i<n; i++) 39 { 40 str[i*2+2] = s[i]; 41 str[i*2+3] = '#'; 42 } 43 n = n*2+2; 44 s[n] = 0; 45 } 46 47 int main() 48 { 49 int i, ans; 50 while(scanf("%s", s)!=EOF) 51 { 52 n = strlen(s); 53 init(); 54 kp(); 55 ans = 0; 56 for(i=0; i<n; i++) 57 if(p[i]>ans) 58 ans = p[i]; 59 printf("%d\n", ans-1); 60 } 61 return 0; 62 }
以上参考