最长重复子串

问题描述

给定一个字符串,求出其最长重复子串
例如:abcdabcd
最长重复子串是 abcd,最长重复子串可以重叠
例如:abcdabcda,这时最长重复子串是 abcda,中间的 a 是被重叠的。

直观的解法是,首先检测长度为 n - 1 的字符串情况,如果不存在重复则检测 n - 2, 一直递减下去,直到 1 。
这种方法的时间复杂度是 O(N * N * N),其中包括三部分,长度纬度、根据长度检测的字符串数目、字符串检测。

改进的方法是利用后缀数组
后缀数组是一种数据结构,对一个字符串生成相应的后缀数组后,然后再排序,排完序依次检测相邻的两个字符串的开头公共部分。
这样的时间复杂度为:生成后缀数组 O(N),排序 O(NlogN*N) 最后面的 N 是因为字符串比较也是 O(N)
依次检测相邻的两个字符串 O(N * N),总的时间复杂度是 O(N^2*logN),优于第一种方法的 O(N^3)


对于类似从给定的文本中,查找其中最长的重复子字符串的问题,可以采用“后缀数组”来高效地完成此任务。后缀数组使用文本本身和n个附加指针(与文本数组相应的指针数组)来表示输入文本中的n个字符的每个子字符串。
首先,如果输入字符串存储在c[0..n-1]中,那么就可以使用类似于下面的代码比较每对子字符串:

[cpp] view plain copy
 
  1. intmain(void)
  2. {
  3. inti,j,thislen,maxlen=-1;
  4. ......
  5. ......
  6. ......
  7. for(i=0;i<n;++i)
  8. {
  9. for(j=i+1;j<n;++j)
  10. {
  11. if((thislen=comlen(&c[i],&c[j]))>maxlen)
  12. {
  13. maxlen=thislen;
  14. maxi=i;
  15. maxj=j;
  16. }
  17. }
  18. }
  19. ......
  20. ......
  21. ......
  22. return0;
  23. }

当作为comlen函数参数的两个字符串长度相等时,该函数便返回这个长度值,从第一个字符开始:

[cpp] view plain copy
 
  1. intcomlen(char*p,char*q)
  2. {
  3. inti=0;
  4. while(*p&&(*p++==*q++))
  5. ++i;
  6. returni;
  7. }

由于该算法查看所有的字符串对,所以它的时间和n的平方成正比。下面便是使用“后缀数组”的解决办法。
如果程序至多可以处理MAXN个字符,这些字符被存储在数组c中:

[cpp] view plain copy
 
  1. #defineMAXCHAR5000//最长处理5000个字符
  2. charc[MAXCHAR],*a[MAXCHAR];

在读取输入时,首先初始化a,这样,每个元素就都指向输入字符串中的相应字符:

[cpp] view plain copy
 
  1. n=0;
  2. while((ch=getchar())!='\n')
  3. {
  4. a[n]=&c[n];
  5. c[n++]=ch;
  6. }
  7. c[n]='\0';//将数组c中的最后一个元素设为空字符,以终止所有字符串

这样,元素a[0]指向整个字符串,下一个元素指向以第二个字符开始的数组的后缀,等等。如若输入字符串为"banana",该数组将表示这些后缀:
a[0]:banana
a[1]:anana
a[2]:nana
a[3]:ana
a[4]:na
a[5]:a
由于数组a中的指针分别指向字符串中的每个后缀,所以将数组a命名为"后缀数组"

第二、对后缀数组进行快速排序,以将后缀相近的(变位词)子串集中在一起
qsort(a, n, sizeof(char*), pstrcmp)后
a[0]:a
a[1]:ana
a[2]:anana
a[3]:banana
a[4]:na
a[5]:nana
第三、使用以下comlen函数对数组进行扫描比较邻接元素,以找出最长重复的字符串:

[cpp] view plain copy
 
  1. for(i=0;i<n-1;++i)
  2. {
  3. temp=comlen(a[i],a[i+1]);
  4. if(temp>maxlen)
  5. {
  6. maxlen=temp;
  7. maxi=i;
  8. }
  9. }
  10. printf("%.*s\n",maxlen,a[maxi]);

完整的实现代码如下:

[cpp] view plain copy
 
  1. #include<iostream>
  2. usingnamespacestd;
  3. #defineMAXCHAR5000//最长处理5000个字符
  4. charc[MAXCHAR],*a[MAXCHAR];
  5. intcomlen(char*p,char*q)
  6. {
  7. inti=0;
  8. while(*p&&(*p++==*q++))
  9. ++i;
  10. returni;
  11. }
  12. intpstrcmp(constvoid*p1,constvoid*p2)
  13. {
  14. returnstrcmp(*(char*const*)p1,*(char*const*)p2);
  15. }
  16. intmain(void)
  17. {
  18. charch;
  19. intn=0;
  20. inti,temp;
  21. intmaxlen=0,maxi=0;
  22. printf("Pleaseinputyourstring:\n");
  23. n=0;
  24. while((ch=getchar())!='\n')
  25. {
  26. a[n]=&c[n];
  27. c[n++]=ch;
  28. }
  29. c[n]='\0';//将数组c中的最后一个元素设为空字符,以终止所有字符串
  30. qsort(a,n,sizeof(char*),pstrcmp);
  31. for(i=0;i<n-1;++i)
  32. {
  33. temp=comlen(a[i],a[i+1]);
  34. if(temp>maxlen)
  35. {
  36. maxlen=temp;
  37. maxi=i;
  38. }
  39. }
  40. printf("%.*s\n",maxlen,a[maxi]);
  41. return0;
  42. }

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
using namespace std;
int complen(char*p, char*q)
{
int i = 0;
while(*p && (*p++ == *q++))
{
++i;
}
return i;
}
int cmp(const char* const p, const char* const q)
{
if (*p <= *q) return true;
else return false;
}
void Display(char** suffix, int len)
{
for(int i = 0; i < len; i++)
{
cout<<suffix[i]<<endl;
}
}
int main()
{
//char str[] = "abcdabcd";
char str[] = "abcdabcda";
int i, tmp;
int maxlen = 0, maxi = 0;
int len = strlen(str);
char** suffix = new char* [len];
for(int i = 0; i < len; i++)
{
suffix[i] = &str[i];
}
//Display(suffix, len);
sort(suffix, suffix+len, cmp);//将后缀数组,按首字母 字典排序
//Display(suffix, len);

for(int i = 0; i < len-1; i++)
{
tmp = complen(suffix[i], suffix[i+1]);
if (tmp > maxlen)
{
maxlen = tmp;
maxi = i;
}
}


printf("%.*s\n", maxlen, suffix[maxi]);


}

方法二:KMP

通过使用next数组的特性,同样可以求最长重复子串,不过时间复杂度有点高挖。。

[cpp] view plain copy
 
  1. #include<iostream>
  2. usingnamespacestd;
  3. constintMAX=100000;
  4. intnext[MAX];
  5. charstr[MAX];
  6. voidGetNext(char*t)
  7. {
  8. intlen=strlen(t);
  9. next[0]=-1;
  10. inti=0,j=-1;
  11. while(i<len)
  12. {
  13. if(j==-1||t[i]==t[j])
  14. {
  15. i++;
  16. j++;
  17. if(t[i]!=t[j])
  18. next[i]=j;
  19. else
  20. next[i]=next[j];
  21. }
  22. else
  23. j=next[j];
  24. }
  25. }
  26. intmain(void)
  27. {
  28. inti,j,index,len;
  29. cout<<"Pleaseinputyourstring:"<<endl;
  30. cin>>str;
  31. char*s=str;
  32. len=0;
  33. for(i=0;*s!='\0';s++,++i)
  34. {
  35. GetNext(s);
  36. for(j=1;j<=strlen(s);++j)
  37. {
  38. if(next[j]>len)
  39. {
  40. len=next[j];
  41. index=i+j;//index是第一个最长重复串在str中的位置
  42. }
  43. }
  44. }
  45. if(len>0)
  46. {
  47. for(i=index-len;i<index;++i)
  48. cout<<str[i];
  49. cout<<endl;
  50. }
  51. else
  52. cout<<"none"<<endl;
  53. return0;
  54. }


题目描述:求最长不重复子串,如abcdefgegcsgcasse,最长不重复子串为abcdefg,长度为7

扫描二维码关注公众号,回复: 622549 查看本文章
[cpp] view plain copy
 
  1. #include<iostream>
  2. #include<list>
  3. usingnamespacestd;
  4. //思路:用一个数组保存字符出现的次数。用i和j进行遍历整个字符串。
  5. //当某个字符没有出现过,次数+1;出现字符已经出现过,次数+1,找到这个字符前面出现的位置的下一个位置,设为i
  6. //并将之前的那些字符次数都-1。继续遍历,直到'\0'
  7. intfind(charstr[],char*output)
  8. {
  9. inti=0,j=0;
  10. intcnt[26]={0};
  11. intres=0,temp=0;
  12. char*out=output;
  13. intfinal;
  14. while(str[j]!='\0')
  15. {
  16. if(cnt[str[j]-'a']==0)
  17. {
  18. cnt[str[j]-'a']++;
  19. }
  20. else
  21. {
  22. cnt[str[j]-'a']++;
  23. while(str[i]!=str[j])
  24. {
  25. cnt[str[i]-'a']--;
  26. i++;
  27. }
  28. cnt[str[i]-'a']--;
  29. i++;
  30. }
  31. j++;
  32. temp=j-i;
  33. if(temp>res)
  34. {
  35. res=temp;
  36. final=i;
  37. }
  38. }
  39. //结果保存在output里面
  40. for(i=0;i<res;++i)
  41. *out++=str[final++];
  42. *out='\0';
  43. returnres;
  44. }
  45. intmain(void)
  46. {
  47. chara[]="abcdefg";
  48. charb[100];
  49. intmax=find(a,b);
  50. cout<<b<<endl;
  51. cout<<max<<endl;
  52. return0;
  53. }




猜你喜欢

转载自vergilwang.iteye.com/blog/2011222