问题描述
给定一个字符串,求出其最长重复子串
例如: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]中,那么就可以使用类似于下面的代码比较每对子字符串:
- intmain(void)
- {
- inti,j,thislen,maxlen=-1;
- ......
- ......
- ......
- for(i=0;i<n;++i)
- {
- for(j=i+1;j<n;++j)
- {
- if((thislen=comlen(&c[i],&c[j]))>maxlen)
- {
- maxlen=thislen;
- maxi=i;
- maxj=j;
- }
- }
- }
- ......
- ......
- ......
- return0;
- }
当作为comlen函数参数的两个字符串长度相等时,该函数便返回这个长度值,从第一个字符开始:
- intcomlen(char*p,char*q)
- {
- inti=0;
- while(*p&&(*p++==*q++))
- ++i;
- returni;
- }
由于该算法查看所有的字符串对,所以它的时间和n的平方成正比。下面便是使用“后缀数组”的解决办法。
如果程序至多可以处理MAXN个字符,这些字符被存储在数组c中:
- #defineMAXCHAR5000//最长处理5000个字符
- charc[MAXCHAR],*a[MAXCHAR];
在读取输入时,首先初始化a,这样,每个元素就都指向输入字符串中的相应字符:
- n=0;
- while((ch=getchar())!='\n')
- {
- a[n]=&c[n];
- c[n++]=ch;
- }
- 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函数对数组进行扫描比较邻接元素,以找出最长重复的字符串:
- for(i=0;i<n-1;++i)
- {
- temp=comlen(a[i],a[i+1]);
- if(temp>maxlen)
- {
- maxlen=temp;
- maxi=i;
- }
- }
- printf("%.*s\n",maxlen,a[maxi]);
完整的实现代码如下:
- #include<iostream>
- usingnamespacestd;
- #defineMAXCHAR5000//最长处理5000个字符
- charc[MAXCHAR],*a[MAXCHAR];
- intcomlen(char*p,char*q)
- {
- inti=0;
- while(*p&&(*p++==*q++))
- ++i;
- returni;
- }
- intpstrcmp(constvoid*p1,constvoid*p2)
- {
- returnstrcmp(*(char*const*)p1,*(char*const*)p2);
- }
- intmain(void)
- {
- charch;
- intn=0;
- inti,temp;
- intmaxlen=0,maxi=0;
- printf("Pleaseinputyourstring:\n");
- n=0;
- while((ch=getchar())!='\n')
- {
- a[n]=&c[n];
- c[n++]=ch;
- }
- c[n]='\0';//将数组c中的最后一个元素设为空字符,以终止所有字符串
- qsort(a,n,sizeof(char*),pstrcmp);
- for(i=0;i<n-1;++i)
- {
- temp=comlen(a[i],a[i+1]);
- if(temp>maxlen)
- {
- maxlen=temp;
- maxi=i;
- }
- }
- printf("%.*s\n",maxlen,a[maxi]);
- return0;
- }
#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数组的特性,同样可以求最长重复子串,不过时间复杂度有点高挖。。
- #include<iostream>
- usingnamespacestd;
- constintMAX=100000;
- intnext[MAX];
- charstr[MAX];
- voidGetNext(char*t)
- {
- intlen=strlen(t);
- next[0]=-1;
- inti=0,j=-1;
- while(i<len)
- {
- if(j==-1||t[i]==t[j])
- {
- i++;
- j++;
- if(t[i]!=t[j])
- next[i]=j;
- else
- next[i]=next[j];
- }
- else
- j=next[j];
- }
- }
- intmain(void)
- {
- inti,j,index,len;
- cout<<"Pleaseinputyourstring:"<<endl;
- cin>>str;
- char*s=str;
- len=0;
- for(i=0;*s!='\0';s++,++i)
- {
- GetNext(s);
- for(j=1;j<=strlen(s);++j)
- {
- if(next[j]>len)
- {
- len=next[j];
- index=i+j;//index是第一个最长重复串在str中的位置
- }
- }
- }
- if(len>0)
- {
- for(i=index-len;i<index;++i)
- cout<<str[i];
- cout<<endl;
- }
- else
- cout<<"none"<<endl;
- return0;
- }
题目描述:求最长不重复子串,如abcdefgegcsgcasse,最长不重复子串为abcdefg,长度为7
- #include<iostream>
- #include<list>
- usingnamespacestd;
- //思路:用一个数组保存字符出现的次数。用i和j进行遍历整个字符串。
- //当某个字符没有出现过,次数+1;出现字符已经出现过,次数+1,找到这个字符前面出现的位置的下一个位置,设为i
- //并将之前的那些字符次数都-1。继续遍历,直到'\0'
- intfind(charstr[],char*output)
- {
- inti=0,j=0;
- intcnt[26]={0};
- intres=0,temp=0;
- char*out=output;
- intfinal;
- while(str[j]!='\0')
- {
- if(cnt[str[j]-'a']==0)
- {
- cnt[str[j]-'a']++;
- }
- else
- {
- cnt[str[j]-'a']++;
- while(str[i]!=str[j])
- {
- cnt[str[i]-'a']--;
- i++;
- }
- cnt[str[i]-'a']--;
- i++;
- }
- j++;
- temp=j-i;
- if(temp>res)
- {
- res=temp;
- final=i;
- }
- }
- //结果保存在output里面
- for(i=0;i<res;++i)
- *out++=str[final++];
- *out='\0';
- returnres;
- }
- intmain(void)
- {
- chara[]="abcdefg";
- charb[100];
- intmax=find(a,b);
- cout<<b<<endl;
- cout<<max<<endl;
- return0;
- }