输入一行字符串,找出其中重复出现且最长的字符串,输出它及其首字符的位置

 1、问题

       输入一行字符串,找出其中出现的相同且长度最长的字符串,输出它。

 

2、解析

       例如“yyabcdabjcabceg”,输出应该为abc和3。

       可以将字符串yyabcdabjcabceg分解成如下子串

       yyabcdabjcabceg

       yabcdabjcabceg

       abcdabjcabceg

       .......

       eg

       g

       1)从最大子串开始正序查找,然后逆序查找,如果两次查找位置不一致说明存在重复子串。

       2)利用后缀数组求解



3、方法一

从最大子串开始正序查找,然后逆序查找,如果两次查找位置不一致说明存在重复子串。

#include <iostream>

#include<string>

using namespace std;

int main()

{

    string str,tep;

    cout<<"请输入字符串"<<endl;

    cin>>str;

    for(int i=str.length()-1;i>1;i--)//这里虽然没有保证i比j大,但是在内层循环的if语句中,有限制j+i<=str.length()

    {

        for(int j=0;j<str.length();j++)

        {

            if(j+i<=str.length())//???这里非常不理解???

            {

                size_t t=0;

                size_t num=0;

                tep=str.substr(j,i);//从大到小取子串,待寻找子串

               //从前后分别查找字符串,正常情况下是刚开始的长串找不到的,到后来的短串前后才会重

                t=str.find(tep);//正序查找子串tep,从0开始查找字符串tep,返回tep的位置

                num=str.rfind(tep);//逆序查找子串tep,从0开始从后向前查找字符串,返回tep的位置

                             

                if(t!=num)//如果两次查找位置不一致说明存在重复子串

                {

                    cout<<tep<<" "<< t+1<<endl;//输出子串及位置

                    return 0;//因为是从长到短,所以再短的也就不找了

                }

            }

        }

    }

    return 0;

}

 

方法2:后缀数组法(好)

  1. #include <iostream>   
  2. using namespace std;  
  3.   
  4. #define MAXCHAR 5000 //最长处理5000个字符   
  5.   
  6. //a存放待处理字符串,*a作为后缀数组,后缀数组a中存放的都是每一个后缀的第一个字符在待处理字符串c中的位置(地址),如a[0] = &c[0], a[1] = &c[1],..., a[n] = &c[n],由于数组a中的指针分别指向字符串中的每个后缀,所以将数组a命名为"后缀数组"

  7. char c[MAXCHAR], *a[MAXCHAR];  

  8.  //返回p,q重复部分长度
  9. int comlen( char *p, char *q )  
  10. {  
  11.     int i = 0;  
  12.     while( *p && (*p++ == *q++) )  
  13.         ++i;  
  14.     return i;  
  15. }  

  16.  //比较两个字符串
  17. int pstrcmp( const void *p1, const void *p2 )  
  18. {  
  19.     return strcmp( *(charconst *)p1, *(charconst*)p2 );  
  20. }  
  21.   
  22.   
  23. int main(void)  
  24. {  
  25.     char ch;  
  26.     int  n=0;  
  27.     int  i, temp;  
  28.     int  maxlen=0, maxi=0;  
  29.     printf("Please input your string:\n");  
  30.   
  31.     n = 0;  
  32.     //在读取输入时,初始化后缀数组a,这样,每个元素就都指向输入字符串中的相应字符:
  33.     while( (ch=getchar())!='\n' )  
  34.     {  
  35.         a[n] = &c[n];  
  36.         c[n++] = ch;  
  37.     }  
  38.     c[n]='\0';     // 将数组c中的最后一个元素设为空字符,以终止所有字符串   

  39.    //对后缀数组进行快速排序,以将后缀相近的子串集中在一起
  40.     qsort( a, n, sizeof(char*), pstrcmp );  

  41.   //使用comlen函数对数组进行扫描比较邻接元素,以找出最长重复的字符串
  42.     for(i = 0 ; i < n-1 ; ++i )  
  43.     {  
  44.         temp=comlen( a[i], a[i+1] );  
  45.         if( temp>maxlen )  
  46.         {  
  47.             maxlen=temp;  
  48.             maxi=i;  
  49.         }  
  50.     }  
  51.     printf("%.*s\n",maxlen, a[maxi]);  
  52.       
  53.     return 0;  
  54. }  


第二部分
题目出自:《程序员面试宝典》(第三版) P226页  面试例题2

       输入一行字符串,找出其中出现的相同且长度最长的字符串,输出它及首字符的位置。例如“yyabcdabjcabceg”,输出结果应该为abc和3。

       这个题目存在一个歧义,例如如果测试字符串为aaaaaa,我一开始以为结果是长度为3的aaa,原来正确答案是长度为5的aaaaa。即2个等长的子串可以有部分字符重叠。因此,面试前最好问仔细点题目意图,免得自己把自己给坑了。

       首先,这题的2个for循环中的那个if语句明显多于,这个if语句可以放到j的for循环中去,  即for(int j=0;j<str.length();j++)改为for(int j=0;j<=str.length()-i;j++)。

       再者,书上给的程序有一个小bug,例如输入测试例子abba,没有任何输出结果,理论上应该输出a:1或者b:2。当测试例子为abca,也没有任何输出结果,理论上应该输出a:1。 这是因为寻找相同的子串时,程序忽略了长度为1的子串,当然这也可能是题目本意。如果不忽略长度为1的子串,直接修改i变量的下限,令for中的i>=1即可。

       书中程序思想: 按照长度递减去寻找相同的子串,只要找到第一对相同的子串,则立刻退出程序。因为是按照长度递减的顺序去寻找子串,所以必定能找到最长的相同子串。

       以abcab为例子分析如下:

       首先寻找长度为4的子串,只能是abca和bcab,再查看这两个子串是否有其他相同的子串。有的话直接输出结果并退出程序。

       然后寻找长度为3的子串,只能abc,bca,cab。这3个子串都没有其他相同的子串。

       最后寻找长度为2的子串,首先是ab,用find函数返回在abcab中正序查找的位置0,用rfind函数返回在abcab中逆序查找的位置3。这两个位置不相等,说明在不同的位置存在相同的子串ab。直接输出ab:1,然后退出程序。

    

       法二:这道题目和前面的面试例题1非常相似,因此两者的思想也相似,法二的代码也是在面试例题1的方法二的代码基础上稍作修改。

       相同子串的首字符必定相等,因此依次遍历str的每个字符作为相等子串的首字符,然后在str中搜索与首字符相等的字符,然后从这两个相等的首字符开始,依次比较下一个字符是否相等,相等的话字符长度就会增加,直到下一个字符不等为止或者到了字符串的末尾。然后把这长度最大的值存放在maxlen中,最后返回长度最大的子串和起始字符的下标。

       程序源代码如下,fun函数是书上的源代码,封装成函数形式,去掉if语句后j的范围优化为j<=len-i,同时i的下限变成i>=1,其他不变。fun1是法二的版本。

[cpp]  view plain copy
  1. #include<iostream>              
  2. #include<string>     
  3. using namespace std;     
  4.     
  5. //书上的源代码,封装成函数形式,j的范围优化了一下,同时i的下限变成i>=1,其他不变    
  6. pair<int,string> fun(const string &str)      
  7. {      
  8.     int count=0;      
  9.     string substr,tep;      
  10.     int i,j,len=str.length();      
  11.     for(i=len-1;i>=1;--i){      
  12.         for(j=0;j<=len-i;j++){      
  13.                 size_t t=0;      
  14.                 size_t num=0;      
  15.                 tep=str.substr(j,i);      
  16.                 t=str.find(tep);      
  17.                 num=str.rfind(tep);      
  18.                 if(t!=num){      
  19.                     count=t+1;      
  20.                     substr=tep;      
  21.                     return make_pair(count,substr);      
  22.                 }       
  23.         }      
  24.     }      
  25.     return make_pair(count,substr);      
  26. }      
  27.           
  28.           
  29. pair<int,string> fun1(const string& str)      
  30. {      
  31.     int index=0;      
  32.     int maxlen=0;      
  33.     string substr;      
  34.     int i=0,j=0;      
  35.     int len=str.length();      
  36.     int k=i+1;      
  37.     int s,lt;    
  38.     while(i<len){      
  39.         j=str.find(str[i],k); //从(k~len-1)范围内寻找str[i]      
  40.         if(j==string::npos  )//若找不到,说明(k~len-1)范围内没有str[i]      
  41.         {      
  42.             i++;      
  43.             k=i+1;      
  44.         }else//若找到,则必有(j>=i+1)      
  45.             s=i;       
  46.             lt=1;      
  47.             while(str[++s]==str[++j] && j<len){}      
  48.             lt=s-i;      
  49.             if(lt>maxlen)      
  50.             {      
  51.                 maxlen=lt;      
  52.                 substr=str.substr(i,lt);      
  53.                 index=i+1;      
  54.             }      
  55.             k=j;      
  56.         }//else      
  57.     }//while      
  58.     return make_pair(index,substr);      
  59. }      
  60.           
  61. int main()      
  62. {      
  63.               
  64.     string str;      
  65.     pair<int,string> rs;      
  66.     while(cin>>str)      
  67.     {      
  68.         rs=fun(str);      
  69.         cout<<rs.second<<":"<<rs.first<<endl;      
  70.         rs=fun1(str);      
  71.         cout<<rs.second<<":"<<rs.first<<endl;      
  72.           
  73.     /*书上源代码    
  74.     int count=0;   
  75.         string substr,tep;   
  76.         int i,j,len=str.length();   
  77.         for(i=len-1;i>1;--i){   
  78.             for(j=0;j<len;j++){   
  79.                 if(j+i<=len){   
  80.                     size_t t=0;   
  81.                     size_t num=0;   
  82.                     tep=str.substr(j,i);   
  83.                     t=str.find(tep);   
  84.                     num=str.rfind(tep);   
  85.                     if(t!=num){   
  86.                         count=t+1;   
  87.                         substr=tep;   
  88.                         cout<<tep<<" : "<<t+1<<endl;   
  89.                         return 0;   
  90.                     }   
  91.                 }   
  92.             }   
  93.         }*/      
  94.                   
  95.           
  96.     }      
  97.     return 0;      
  98. }    

注:转载源地址
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值