统计一个字符串中不相同的回文子串数量的O(nlogn)算法
- 格式:doc
- 大小:151.00 KB
- 文档页数:6
leetcode 字符串的不同子字符串个数标题:深度解析:leetcode字符串的不同子字符串个数在计算机编程中,字符串处理一直是一个重要而又复杂的主题。
而针对字符串的算法问题,leetcode评台上的问题一直备受关注。
其中一个经典问题就是计算一个字符串中不同子字符串的个数。
本文将深入探讨这个问题,以及解决这个问题的相关算法和技巧。
1. 问题概述在leetcode上,有一道经典的问题是求解字符串中不同子字符串的个数。
这个问题要求在给定一个字符串的情况下,计算出该字符串中不同子字符串的个数。
这个问题看似简单,但实际上涉及到了很多复杂的算法和技巧。
2. 基本解法最基本的解法就是暴力法,即枚举出所有的子字符串,然后利用哈希表或者集合来存储不同的子字符串。
然而,这个解法时间复杂度为O(n^3),在面对大规模字符串的情况下效率低下。
3. 优化解法为了优化暴力解法,我们可以利用动态规划或者滑动窗口来解决这个问题。
使用动态规划,可以减少重复计算,将时间复杂度降低至O(n^2)。
而滑动窗口则可以在O(n)的时间复杂度内解决这个问题。
4. 进阶算法除了基本解法和优化解法外,还有一些进阶的算法可以解决这个问题。
比如利用字典树或者后缀数组来处理不同子字符串的计算。
这些算法在特定场景下有很好的效果,但也需要深入的理解和技巧。
5. 个人观点在解决这个问题的过程中,我个人认为动态规划是一个非常重要的思维方式。
通过合理地定义状态和状态转移方程,可以解决很多复杂的字符串处理问题。
而滑动窗口算法则是一种非常巧妙和高效的技巧,特别适合处理子字符串的情况。
以上就是我对leetcode字符串不同子字符串个数问题的深度解析。
希望可以帮助你更深入地理解这个问题,并且在解决类似问题时能够有所帮助。
(字数:超过3000字)6. 动态规划的应用在动态规划的应用中,我们可以将字符串的每一个位置作为结尾,然后利用状态转移方程来计算以该位置结尾的不同子字符串的个数。
O(n)回文子串算法这里,我介绍一下O(n)回文串处理的一种方法。
Manacher算法.原文地址:/2009/08/02/a-simple-lin ear-time-algorithm-for-finding-longest-palindrome-sub-str ing/其实原文说得是比较清楚的,只是英文的,我这里写一份中文的吧。
首先:大家都知道什么叫回文串吧,这个算法要解决的就是一个字符串中最长的回文子串有多长。
这个算法可以在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数组求出来,最长回文子串就可以直接扫一遍得出来了。
重复字符串算法重复字符串问题是计算机编程中经常遇到的问题之一,也常常被用来测试算法的效率。
在此次算法中,我们将讨论如何在给定的字符串中寻找是否存在重复的子字符串,并给出解决方法。
一、问题描述重复字符串问题可以简述如下:给定一个字符串S,需要查找S中是否存在两个不同的子串相同的情况。
如果存在,则返回true,否则返回false。
例如,字符串S=“abcdefghijklmnopqrstuvwxyz”中不存在重复子字符串,并且字符串S=“abababc”中存在相同的子字符串“ab”。
二、方法一一种解决重复字符串问题的方法是暴力枚举。
该方法从字符串的第一个字符开始依次枚举所有可能的子串,并检查是否存在相同的子串。
代码实现如下:该方法的时间复杂度为O(n^3),其中n是字符串S的长度。
因此,该方法对于较长的字符串是不实用的。
三、方法二另一种解决重复字符串问题的方法是Rabin-Karp算法。
该算法利用哈希函数将字符串转换为数字,然后通过比较数字来判断是否存在相同的子串。
具体地,该算法首先选取一个质数p和一个基数x,然后计算字符串S的哈希值H(S),公式为:H(S) = S[0] * x^(n-1) + S[1] * x^(n-2) + ... + S[n-2] * x^1 + S[n-1] * x^0其中,S[i]表示字符串S的第i个字符,n是字符串S的长度。
然后,依次计算S中长度为k(k是固定的)的子串的哈希值,并判断是否存在相同的哈希值。
如果存在相同的哈希值,则再用字符串比较方法检查是否存在相同的子串。
四、方法三一种优化重复字符串问题的方法是后缀数组(Suffix Array)。
后缀数组是字符串S的所有后缀按照字典序排序后所得到的数组,它可以用来快速求解字符串相关问题,包括最长公共前缀、最长重复子串等。
具体地,该算法首先创建字符串S的后缀数组,然后通过比较相邻的后缀来寻找最长的重复子串。
如果存在多个长度相等的重复子串,则返回任意一个即可。
“abcab”是一个由5个字符a、b、c组成的字符串。
如果我们要求找出所有本质不同的子串,那么我们需要考虑子串中字符的出现顺序和位置。
首先,我们需要明确什么是本质不同的子串。
一个子串如果只通过字符的替换、删除或翻转,无法得到另一个不同的子串,那么我们就称这两个子串本质不同。
例如,“abc”和“acb”在“abcab”中就是本质不同的子串,因为它们可以通过翻转第一个字符得到。
为了找出所有本质不同的子串,我们可以采用动态规划的方法。
设dp[i]表示以第i个字符结尾的本质不同的子串个数。
显然,当i=5时,只有一种子串:abcab。
对于i<5的情况,我们可以通过遍历所有可能的子串,然后根据前一个状态的结果来更新当前状态。
具体的状态转移方程可以表示为:dp[i]=dp[i-1]+(1)(只有第i个字符不同)dp[i]=dp[i-3](以第i-1个字符结尾、第i-2个字符插入后得到的子串本质不同)因此,我们可以使用循环来遍历所有可能的子串,并使用上述状态转移方程来更新dp数组。
最终的结果就是dp[5],即abcab中本质不同的子串个数。
根据上述分析,我们可以得出结论:abcab中本质不同的子串个数为4。
具体来说,有以下4种情况:abc、acb、bca和cab。
这是因为我们可以通过替换、删除或翻转字符得到这些子串,而无法得到其他不同的子串。
综上所述,abcab字符串本质不同的子串个数为4,这是一个需要精心设计和计算的问题,需要对动态规划和字符串操作有深入的理解才能得出正确的答案。
同时,这个问题也提醒我们,在实际问题中,我们应该善于思考和挖掘问题的本质,通过对问题的深入分析和计算,找到合适的解决方案。
此外,对于类似的问题,我们也可以通过归纳和抽象的方法,将其推广到一般的情况,从而更好地解决实际问题。
go 无重复字符串排列组合算法Go语言无重复字符串排列组合算法在计算机科学中,排列组合是个常见的问题。
即给定一个字符串,求其所有可能的排列或组合。
例如,给定字符串"abc",则其可能的排列有"abc"、"acb"、"bac"、"bca"、"cab"、"cba",而可能的组合有"a"、"b"、"c"、"ab"、"ac"、"bc"、"abc"。
本文将介绍如何使用Go语言编写一个无重复字符串的排列组合算法。
在开始之前,我们需要明确一些基本概念。
一个字符串的排列是指由该字符串中的元素所组成的各种可能的顺序。
一个字符串的组合是指由该字符串中的元素所组成的各种可能的子集。
在我们的算法中,我们将使用递归的方式来生成所有可能的排列和组合。
首先,我们需要定义一个函数来生成字符串的排列。
该函数接受两个参数:原始字符串和当前生成的排列。
基本思路是用当前生成的排列和原始字符串中的一个字符交换位置,然后递归调用函数,直到生成了所有可能的排列。
```gofunc permute(str string, prefix string) {if len(str) == 0 {fmt.Println(prefix)return}for i := 0; i < len(str); i++ {newStr := str[:i] + str[i+1:]permute(newStr, prefix+string(str[i]))}}```接下来,我们需要定义一个函数来生成字符串的组合。
该函数接受两个参数:原始字符串和当前生成的组合。
基本思路是对于每个字符,有两种选择:包括该字符或者不包括该字符。
求回⽂数算法问题:求第N个回⽂数palindrome。
⼀个正数如果顺着和反过来都是⼀样的(如13431,反过来也是13431),就称为回⽂数。
约束:回⽂数不能以0开头。
回⽂数从1开始。
⾸先我们要写⼀个算法求回⽂数。
刚开始我想到⽤⽤字符串来存储数,然后判断原序和逆序是否相等。
void func1(char a[]){printf("%d",strlen(a));char *p=a;char *q=a+strlen(a)-1;bool flag=true;while(q>p){if(*q!=*p){flag=false;break;}q--;p++;}if(flag)printf("%s 是回⽂数\n",a);elseprintf("%s 不是回⽂数\n",a);}int main(){char s[50];while(scanf("%s",s)!=0){printf("%d",strlen(s));func1(s);}注意,⽤strlen的时候只检测什么时候到‘\0'位置,与sizeof⽆关,如果是sizeof的话char a[]做函数参数a会降级为指针。
虽然这样做可以,但还是有点⼩问题,如果输⼊010,输出回⽂。
不满⾜回⽂的定义。
回⽂数不能以0开头。
⼀开始就思考使⽤循环:从1开始,判断该数是否是回⽂数,然后⽤⼀个计数器记下回⽂数,⼀直到计数器得到N,返回第N个回⽂数。
⽐较常⽤的是以下这种⽅法来判断是否回⽂数:static boolean isPN(int num) {int o = num;int tmp = 0;//使⽤循环把数字顺序反转while(num != 0) {tmp *= 10;tmp += num % 10;num /= 10;}//如果原始数与反转后的数相等则返回trueif(tmp == o)return true;return false;}这种思路的确可得到正确结果,但随着⽤来测试的N的增⼤,效率的问题就浮现了。
后缀数组、不重复子串Distinct Substrings题目大意:给出一个字符串,问它的不重复子串有多少个。
两题是一样的,除了字符串长度..分析:用后缀数组可以轻松解决。
因为这个字符串的每个子串必然是某个后缀的前缀,先用后缀数组求出sa和height,那么对于sa[k],它有n-sa[k]个子串,其中有height[k]个是和上一个后缀重复的,所以要减去。
所以用后缀数组求解的时间复杂度是O(n),后缀数组要是用倍增算法是O(nlog2n),效率很高。
note:wa了一次,主要原因是忘了a[n]=0这个关键的初值...PS:各位大牛对我的差劲的c++代码有什么看法可以尽管喷哈!codes:#include<iostream>#include<cstring>using namespace std;const long maxn=1010;long wn[maxn],wa[maxn],wb[maxn],wv[maxn],a[maxn],sa[maxn],rank[maxn],height[maxn]; char r[maxn];long cmp(long *r,long a,long b,long l){return r[a]==r[b]&&r[a+l]==r[b+l];}void da(long *r,long *sa,long n,long m){long i,j,p,*x=wa,*y=wb,*t;for (i=0;i<m;i++) wn[i]=0;for (i=0;i<n;i++) wn[x[i]=r[i]]++;for (i=1;i<m;i++) wn[i]+=wn[i-1];for (i=n-1;i>=0;i--) sa[--wn[x[i]]]=i;for (p=1,j=1;p<n;j*=2,m=p){for (p=0,i=n-j;i<n;i++) y[p++]=i;for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;for (i=0;i<m;i++) wn[i]=0;for (i=0;i<n;i++) wn[wv[i]=x[y[i]]]++;for (i=1;i<m;i++) wn[i]+=wn[i-1];for (i=n-1;i>=0;i--) sa[--wn[wv[i]]]=y[i];for (t=x,x=y,y=t,x[sa[0]]=0,p=1,i=1;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}return;}void calheight(long *r,long *sa,long n){long i,j,k=0;for (i=1;i<=n;i++) {rank[sa[i]]=i;height[i]=0;}for (i=0;i<n;height[rank[i++]]=k)for (k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);return;}int main(){long t,i;cin >> t;while (t--){cin >> r;long n=strlen(r);for (int i=0;i<n;i++) a[i]=static_cast<int>(r[i]);a[n]=0;da(a,sa,n+1,256);calheight(a,sa,n);long sum=0;for (i=1;i<=n;i++) sum+=n-sa[i]-height[i];cout << sum << endl;}return 0;}----------------------------------------------------------------------------------------------------------------------------[后缀数组]最长重复子串分析:任何一个重复子串,必然是某两个后缀的公共前缀。
字母不重复的子串-概述说明以及解释1.引言1.1 概述字母不重复的子串是指在一个字符串中,找出不包含重复字母的最长子串。
也就是说,子串中的每个字符在该子串中只出现一次。
这个问题在字符串处理和算法设计方面有着广泛的应用。
在实际生活中,我们经常需要处理包含字母的字符串,例如英文单词、句子甚至是DNA序列。
而找出其中不重复的子串,能够帮助我们理解字符串的特征和模式,从而进行更深入的分析和处理。
字母不重复的子串问题在实际应用中有着很广泛的应用。
以文本信息处理为例,比如在自然语言处理中,我们需要通过分析文本中的子串来提取关键词、识别命名实体等。
另外,在数据压缩和加密中,也经常需要使用字母不重复的子串来提高存储和传输的效率。
本文将深入探讨字母不重复的子串问题的意义和应用,并通过具体的算法设计与实例分析,展示如何高效地找出字母不重复的子串。
最后,我们将对问题进行总结,并展望未来该领域可能的发展方向。
让我们一起来探索吧!1.2 文章结构本文将分为三个主要部分进行讨论。
第一部分是引言部分。
在这部分中,我们将概述本文的主要内容,包括字母不重复的子串的概念和意义,以及本文的结构和目的。
第二部分是正文部分。
在这部分中,我们将详细讨论字母不重复的子串的意义和应用。
首先,我们将探讨字母不重复的子串在计算机科学领域的重要性,并介绍一些相关的算法和数据结构。
然后,我们将探讨字母不重复的子串在其他领域的应用,如自然语言处理、图像处理等。
通过这些案例研究,我们将阐述字母不重复的子串在现实生活中的实际应用和潜在价值。
第三部分是结论部分。
在这部分中,我们将对前文进行总结,并提出一些展望。
我们将回顾字母不重复的子串对于解决实际问题的作用,并讨论可能的未来研究方向。
我们还将提供一些实用建议,以帮助读者更好地理解和应用字母不重复的子串的概念。
通过以上结构,本文旨在全面介绍字母不重复的子串的意义和应用,并为读者提供深入理解和有效运用该概念的指导。
各种排序笔记---基于⾮⽐较排序部分在计算机科学中,排序是⼀门基础的算法技术,许多算法都要以此作为基础,不同的排序算法有着不同的时间开销和空间开销。
排序算法有⾮常多种,如我们最常⽤的快速排序和堆排序等算法,这些算法需要对序列中的数据进⾏⽐较,因为被称为基于⽐较的排序。
基于⽐较的排序算法是不能突破O(NlogN)的。
简单证明如下:N个数有N!个可能的排列情况,也就是说基于⽐较的排序算法的判定树有N!个叶⼦结点,⽐较次数⾄少为log(N!)=O(NlogN)(斯特林公式)。
⽽⾮基于⽐较的排序,如计数排序,桶排序,和在此基础上的基数排序,则可以突破O(NlogN)时间下限。
但要注意的是,⾮基于⽐较的排序算法的使⽤都是有条件限制的,例如元素的⼤⼩限制,相反,基于⽐较的排序则没有这种限制(在⼀定范围内)。
但并⾮因为有条件限制就会使⾮基于⽐较的排序算法变得⽆⽤,对于特定场合有着特殊的性质数据,⾮基于⽐较的排序算法则能够⾮常巧妙地解决。
基于⾮⽐较的排序算法有三种,计数排序,桶排序和基数排序。
-----------------------------我是分割线-------------------------------------------------------1. 计数排序计数排序(Counting sort)是⼀种稳定的。
计数排序使⽤⼀个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。
然后根据数组C来将A中的元素排到正确的位置。
特征:当输⼊的元素是n个0到k之间的整数时,它的运⾏时间是Θ(n + k)。
计数排序不是,排序的速度快于任何⽐较排序算法。
由于⽤来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最⼤值与最⼩值的差加上1),这使得计数排序对于数据范围很⼤的数组,需要⼤量时间和内存。
例如:计数排序是⽤来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序⼈名。
编写程序统计1行字符串中:不同字符的个数。
每种字符出现的次数。
编写一个程序来统计给定字符串中不同字符的个数以及每种字符出现的次数。
首先,我们需要定义一个函数来实现这个功能。
```pythondef count_characters(string):# 创建一个空的字典来存储字符和它们的出现次数character_count = {}# 遍历字符串中的每个字符for char in string:# 如果字符已经在字典中,则将其计数加1if char in character_count:character_count[char] += 1# 如果字符不在字典中,则将其添加到字典中,并将计数设置为1else:character_count[char] = 1# 打印不同字符的个数print('不同字符的个数:', len(character_count))# 打印每个字符和它们的出现次数for char, count in character_count.items():print('字符 '{}' 出现次数: {}'.format(char, count))# 测试程序string = input('请输入一个字符串:')count_characters(string)```在上述程序中,我们首先创建了一个空的字典 `character_count` 来存储字符和它们的出现次数。
然后,我们遍历给定字符串中的每个字符,并检查它是否已经在字典中。
如果是,则将其计数加1;如果不是,则将其添加到字典中,并将计数设置为1。
最后,我们打印出不同字符的个数,并以字符和它们的出现次数的形式打印出结果。
请注意,上述代码仅适用于英文字符。
如果要处理其他语言的字符,需要对代码进行适当的修改。
《计算模型与算法技术》课程设计题目: 统计一个字符串中不相同的回文子串数量的O(nlogn)算法院(系):软件学院专业:10软件三班学生姓名:张云帆201030633064指导教师:黄翰提交日期: 2012/6/29统计一个字符串中不相同的回文子串数量的O(nlogn)算法一.问题描述求长为N的字符串中不相同的回文子串数量。
二.研究回顾.2.1 分段介绍文献[1]介绍了用倍增算法和DC3算法求后缀数组的方法,时间复杂度为别为O(nlogn)和O(n)。
文献[1]介绍了求height数组的方法,时间复杂度为O(n)。
文献[1]介绍了将求一个字符串中某两个后缀的最长公共前缀问题转化为RMQ问题的思路,时间复杂度为O(nlogn)或O(n)。
文献[1]介绍了统计一个字符串中不相同的子串的个数的思路,时间复杂度为求后缀数组的时间复杂度。
文献[1]介绍了将求一个字符串中最长回文子串的长度问题转化为某两个后缀的最长公共前缀问题的思路,时间复杂度为求后缀数组和求RMQ算法的时间复杂度的上界。
2.2 参考文献[1]罗穗骞. 后缀数组——处理字符串的有力工具. 2009三.算法描述3,1 算法的思路1)将字符串翻转后用一个特殊字符间隔连接到原字符串后,用n表示新字符串长度,len表示原字符串长度,0为起始下标。
2)用倍增算法在O(nlogn)的时间复杂度内求出后缀数组sa。
3)用文献[1]中介绍介绍的算法在O(n)的时间复杂度内求出名次数组rank和height数组。
4)用Sparse Table算法在O(nlogn)的时间复杂度内求出best数组。
5)顺序扫描height数组,在扫描时更新cnt1和cnt2的数值。
cnt1和cnt2分别表示以sa[i]为中心的奇数回文子串和偶数回文子串中包含的已经统计过的相同回文子串的数量。
如果sa[i] < len,计算以sa[i]为中心的最长回文子串长度。
6)分为奇数长度和偶数长度两种情况处理,如果以sa[i]为中心的最长回文子串长度now大于对应的cnt数值,则更新不同回文子串数量以及对应的cnt数值。
3.2 算法核心代码/*待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m。
为了函数操作的方便,约定除s[n-1]外所有的s[i]都大于0,s[n-1]=0。
函数结束后,结果放在sa数组中,从sa[0]到sa[n-1]。
*/inline void suffix(int n, int m = 128){ //用倍增算法求后缀数组int i, l, p, *x = X, *y = Y;//使用基数排序对长度为1的字符串进行排序。
for (i = 0; i < m; i++) buc[i] = 0;for (i = 0; i < n; i++) buc[x[i] = s[i]]++;for (i = 1; i < m; i++) buc[i] += buc[i - 1];for (i = n - 1; i >= 0; i--) sa[--buc[x[i]]] = i;//进行若干次基数排序,基数排序要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。
对第二关键字排序的结果可以利用上一次求得的sa直接算出。
for (l = 1, p = 1; p < n; m = p, l *= 2) {p = 0;for (i = n - l; i < n; i++) y[p++] = i;for (i = 0; i < n; i++)if (sa[i] >= l) y[p++] = sa[i] - l; //变量l是当前字符串的长度,数组y保存的是对第二关键字排序的结果for (i = 0; i < m; i++) buc[i] = 0;for (i = 0; i < n; i++) buc[x[y[i]]]++;for (i = 1; i < m; i++) buc[i] += buc[i - 1];for (i = n - 1; i >= 0; i--) sa[--buc[x[y[i]]]] = y[i];//计算rank值for (swap(x,y), x[sa[0]] = 0, i = 1, p = 1; i < n; i++)x[sa[i]] = cmp(y, sa[i-1], sa[i], l) ? p - 1 : p++;}calheight(n - 1);//后缀数组关键是求出height,所以求sa的时候顺便把rank和height求出来}时间复杂度分析:每次基数排序的时间复杂度为O(n),排序的次数决定于最长公共子串的长度,最坏情况下,排序次数为logn次,所以总的时间复杂度为O(nlogn)[1]。
/*height数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。
*/inline void calheight(int n){int i, j, k = 0;for (i = 1; i <= n; i++) rank[sa[i]] = i;for (i = 0; i < n; height[rank[i++]] = k)for (k ? k-- : 0, j = sa[rank[i] - 1]; s[i + k] == s[j + k]; k++);}时间复杂度:O(n)/*best[j][i]表示height[i]到height[i + 2^j - 1]的最小值。
初始化best[0][i] = height[i],best[i][j] = min(best[i - 1][j], best[i - 1][j + (1 << i - 1)])。
*/inline void initRMQ(int n){ //初始化RMQfor(int i = 1; i <= n; i++) best[0][i] = height[i];int t = log2(n);for(int i = 1; i <= t; i++) {int limit = n - (1 << i) + 1;for(int j = 1; j <= limit; j++)best[i][j] = min(best[i - 1][j], best[i - 1][j + (1 << i - 1)]);}}时间复杂度分析:j最大为log(n),i最大为n,所以总的时间复杂度为O(nlogn)。
inline int lcp(int a, int b){ //询问a,b后缀的最长公共前缀if (a == b) return strlen(s) - b;a = rank[a],b = rank[b];if(a > b) swap(a, b);a++;int t = log2(b - a + 1);return min(best[t][a], best[t][b - (1 << t) + 1]);}时间复杂度:O(1)for (int i = 1; i <= n; i++){ //统计不同的回文子串数量cnt1 = min(cnt1, height[i]), cnt2 = min(cnt2, height[i]); //cnt1和cnt2分别表示以sa[i]为中心的奇数回文子串和偶数回文子串中包含的已经统计过的相同回文子串的数量if (sa[i] >= len) continue; //sa[i]不属于原字符串则跳过,避免重复统计以及简化处理int now = lcp(sa[i], n - sa[i] - 1); //now表示以sa[i]为中心的奇数长度最长回文串长度if (now > cnt1) { //有和之前统计过的不同回文子串存在ans += now - cnt1;cnt1 = now;}if (!sa[i]) continue;now = lcp(sa[i], n - sa[i]); //now表示以sa[i]为中心的偶数长度最长回文串长度if (now > cnt2) { //有和之前统计过的不同回文子串存在ans += now - cnt2;cnt2 = now;}}时间复杂度:O(n)3.3 方法关键点陈述方法1 用倍增算法在O(nlogn)的时间复杂度内求出后缀数组sa [1]。
方法2 用Sparse Table算法在O(nlogn)的时间复杂度内求出best数组。
方法3 用线性扫描在O(n)的时间复杂度内完成不同的回文子串数量统计。
3.4 提出方法的创新性创新点1:回文判重的处理方法非常简洁,通过线性扫描统计不同回文子串数量的主算法的常数小创新点2:算法的时间复杂度上界的优化方法非常明确,用DC3算法代替倍增算法使求后缀数组的时间复杂度降到O(n);将RMQ问题通过笛卡儿树转化为LCA问题,可以在O(n)的时间复杂度内解决。
这样整个算法的时间复杂度就能降到O(n)。
四.实验设置4.1 测试问题求长度为N的字符串中的不同回文子串数量。
4.2 参数设置A组:纯随机数据B组:同一个字符C组:均为分100组,组内为同一个字符D组:随机分为若干组,组的长度为随机长度,组内为同一个字符4.3 其他设置Windows 7 + Intel Core 2 P7450 @ 2.13GHz五.实验结果与分析5.1 实验的观察指标程序执行时间5.2 实验结果表1N A组B组C组D组200000 0.659 0.609 0.538 0.546500000 2.266 1.523 1.433 1.4301000000 5.088 3.144 2.977 3.145表1是N为200000、500000、1000000时各组数据的程序执行时间,结果说明此算法的时间复杂度稳定性较好,对于4种不同的数据的运行时间差别不大以及执行效率非常优秀。
5.3 结果分析表1结果出现原因:整个程序用到的都是比较稳定的算法,没有随机因素的影响,所以对于多种数据都能保证稳定的执行效率。
而执行效率非常优秀则是因为算法的常数较小,与O(n)算法的效果相差不大但编程复杂度和思维复杂度远低于其。
分析总结:本文提出的算法对于解决求字符串中的不同回文子串数量问题而言是一个易于实现且效率优秀的算法,足够解决绝大多数情况下的应用需求。
六.实验结论通过对这一问题的研究我对于后缀数组的应用相比以前有了更多的体会和领悟,通过灵活地转换问题模型可以将许多字符串问题通过后缀数组高效地解决。