这是一道Codility上的题目,见 https://codility.com/demo/take-sample-test/gamma2011/ 大意就是给定一个字符串,求出它的所有长度大于2的回文子串的个数。这与求字符串的最长回文子串的问题是等价的。
首先想到的就是遍历字符串的所有长度大于2的子串(共有2+3+...+n-1 = (n+1)(n-2)/2个),对每个子串判断是否为回文串,时间复杂度为O(n^3)
然后,我们注意到,所有的回文子串都有一个中心位置,而对于长为n的字符串,能作为回文子串中心位置的只有2n-1种可能(对于本问题,其实只有2n-3种可能,需要去除第一个和最后一个字符所在的位置),因此,只需遍历这些可能的位置,找出以该位置为中心的回文子串的数目,时间复杂度是O(n^2)。
看起来,似乎这种方法是最优的,毕竟,有2n-3个位置需要判断,而每次判断的时间复杂度是O(n),因此最终时间复杂度是O(n^2),看起来是不能再改善了。
但是呢,世界就是这么奇妙,就像神奇的KMP字符串匹配算法一样,居然能把字符串匹配的时间复杂度做到O(n+m)。对本问题,其实是存在O(n)时间复杂度的算法的!
为了统一处理奇数长度和偶数长度的回文串,我们在每个原字符串的每个字符之间加上一个分隔符#,并且在字符串开头加上一个终结符$,因此,当原字符串是baababa时,转换后的字符串是$#b#a#a#b#a#b#a#,这样,当回文中心是#字符时,回文长度为偶数;当回文中心不是#时,回文长度为奇数。
该算法的基本思想就是扫描处理后的字符串S,记录以每个字符为中心时的最长回文串,同时更新全局的最长回文串记录。描述如下:
待处理字符串为S,长度为N
P为长为n的数组,P[i]记录以字符i为中心的最长回文串的半边长度加1(回文串长度为 2*(P[i]-1) + 1)
K为之前的回文串到达的最右位置,j为该回文串的的中心位置
for i = 1..len(s)-1
初始化P[i]
逐渐增加P[i],直至不满足回文串条件
统计该回文串中回文数目
if i+P[i] > K
K = i + P[i]
j = i
但看描述,似乎与O(n^2)的算法一样,但该算法的关键是初始化P[i],如果每次循环之前P[i]能尽量大,那么每次循环的比较次数就很少,从而降低了复杂度。(具体时间复杂度分析比较复杂,本文不作精确分析)
那么如何确定一个较大的P[i]值呢?
不妨假设当前扫描到第i个字符,根据全局K和j值,我们有:
index 2j-i j i K
char/str A S[i] B S[j] B S[i] A S[K]
由于以j为中心的回文串的最右位置为K(不包含),当K大于i的情况下,不妨假设i与K之间的字符串为A,j与i之间的字符串为B,那么根据回文串性质有2j-i与j之间字符串为B,2j-i之前存在字符串A。
可以利用2j-i与i位置的对称性,不妨假设P[2j-i] = x,那么说明B字符串的前x-1个字符与A字符串的后x-1个字符相同,因此对于i来说,只需要从i+x开始判断即可,如果i+x大于K,那么则从K开始递增。
具体代码(针对Codility题目)如下,亦可参见 https://github.com/phiphy/codility/blob/master/count_palindromic_slices.c
int imin(int x, int y) { return x < y ? x : y; }
int solution ( char *t ) {
if (!t)
return 0;
int n = strlen(t);
if (n < 2)
return 0;
int i, j, k, r = 0;
int m = n + n + 3;
char *s = (char *)malloc(sizeof(char) * m);
int *p = (int *)malloc(sizeof(int) * m);
for (s[0] = '$', s[1] = '#', i = 0, j = 2; i < n; ++i) {
s[j++] = t[i];
s[j++] = '#';
}
s[j] = '\0';
for (k = 0, i = 1; i < m-1; ++i) {
p[i] = k > i ? imin(p[2*j-i], k-i) : 1; // update p[i], as large as possible
while (s[i+p[i]] == s[i-p[i]])
++p[i];
if (p[i] > 1) {
r += (p[i]/2 - (s[i]!='#'));
if (r > 100000000) {
r = -1;
break;
}
}
if (p[i]+i > k) {
k = p[i] + i;
j = i;
}
}
free(s); s = NULL;
free(p); p = NULL;
return r;
}
听说这道题也可以用后缀数组解决,不过我暂时还不懂后缀数组,这段时间研究一下。
No comments:
Post a Comment