最长重复子串和最长不重复子串求解

最长重复子串和最长不重复子串求解

本文内容框架:

§1 最长重复子串

基本方法、KMP算法求解、后缀数组求解

§2 最长不重复子串

基本方法、动态规划、动态规划+Hash

§3 小结

 

§1最长重复子串

 

1.1问题描述

 

首先这是一个单字符串问题。子字符串R 在字符串L 中至少出现两次,则称R 是L 的重复子串。重复子串又分为可重叠重复子串和不可重叠重复子串。

 

1.2基本方法

 

枚举子串,让子串和子串进行比较。直接看代码:

C代码  收藏代码
  1. /* 最长重复子串 Longest Repeat Substring */  
  2.    
  3. int maxlen;    /* 记录最长重复子串长度 */  
  4. int maxindex;  /* 记录最长重复子串的起始位置 */  
  5. void outputLRS(char * arr);  /* 输出LRS */  
  6.    
  7. /* 最长重复子串 基本算法 */  
  8. int comlen(char * p, char * q)  
  9. {  
  10.     int len = 0;  
  11.     while(*p && *q && *p++ == *q++)  
  12.     {  
  13.         ++len;  
  14.     }  
  15.     return len;  
  16. }  
  17.    
  18. void LRS_base(char * arr, int size)  
  19. {  
  20.     for(int i = 0; i < size; ++i)  
  21.     {  
  22.         for(int j = i+1; j < size; ++j)  
  23.         {  
  24.             int len = comlen(&arr[i],&arr[j]);  
  25.             if(len > maxlen)  
  26.             {  
  27.                 maxlen = len;  
  28.                 maxindex = i;  
  29.             }  
  30.         }  
  31.     }  
  32.     outputLRS(arr);  
  33. }  

 ╝①

优化思路

一般的优化方法就是在充分利用已有的结果,最长重复子串的长度增加一定是建立在之前已经找到的重复子串之上的,充分利用已找到的重复子串的位置和长度是优化的一个重点,此外还有就是未不是重复子串的,以后就不会被包含在重复子串内,如"ab"只有一个,则重复子串就不能包含"ab"(允许重叠的重复子串例外)。

 

1.2KMP算法求解

 

对KMP算法还不是很了解的,可以查看我的另一篇博文(不懂猛点),在KMP算法的关键就是求解next数组,针对next[j]=k,可以得到P[0,1,...,k-1]=P[j-k,j-k+1,...,j-1]。看到P[0,1,...,k-1]=P[j-k,j-k+1,...,j-1]应该会眼前一亮,大脑顿时清醒些,这不就是重复子串吗!由此求解最长重复子串就转化为求解KMP算法next数组中的最大值(即max{next[j]=k}。

现在就是求解next数组的问题了,还是直接查看代码:

 

C代码  收藏代码
  1. int getNext(char *str,int *next)  
  2. {  
  3.     int len=strlen(str);  
  4.     int index=0;  
  5.     int k=-1;  
  6.     next[0]=k;  
  7.     int max=0;  
  8.   
  9.     //kmp算法求next值,取得最大的字串  
  10.     while (index<len)  
  11.     {  
  12.         if (k==-1 || str[index]==str[k])  
  13.         {  
  14.             k++;  
  15.             index++;  
  16.             next[index]=k;  
  17.             if (k>max)//求得其中重复最大的字串的个数,也就是与最前面串的重复数  
  18.             {  
  19.                 max=k;  
  20.             }  
  21.         }  
  22.         else  
  23.             k=next[k];  
  24.     }  
  25.   
  26.     return max;  
  27. }  
  28.   
  29. int main()  
  30. {  
  31.     char str[50];//输入字符串  
  32.     cin>>str;  
  33.     int max=0;//最大的字串  
  34.     int nextMax;//接受getNext函数中返回的最大值  
  35.     int index;  
  36.     int maxIndex;//保存最大的字串起始位置  
  37.     int len=strlen(str);  
  38.     //将一个字符串从开始一直减少到只剩一个字符,通过这个取得最小字串  
  39.     for (index=0;index<len-1;index++)  
  40.     {  
  41.         int *next=new int[len-index];//取得next在这没用  
  42.         nextMax=getNext(str+index,next);//每次从str+index开始  
  43.         if (nextMax>max)  
  44.         {  
  45.             max=nextMax;  
  46.             maxIndex=index;  
  47.         }  
  48.     }  
  49.       
  50.     //输出最长字串  
  51.     cout<<"最长字串: ";  
  52.     for (index=0;index<max;index++)  
  53.     {  
  54.         cout<<str[index+maxIndex];  
  55.     }  
  56.     cout<<endl;  
  57.       
  58.     return 0;  
  59. }  

╝② 

 

1.3后缀数组求解

 

后缀数组在我的另外一篇博文有介绍,还没有概念的可以移步查看点击。后缀数组就是保留字符串所有位置到字符串末尾的子字符串,a[i]就是第i位置到末尾的子串。有了后缀数组,对后缀数组进行排序,然后进行求后缀数组相邻元素的最大前缀就是最大重复子串。

 

Cpp代码  收藏代码
  1. #include <iostream>  
  2.  using namespace std;  
  3.     
  4.  const int MAXN = 1000;  
  5.   
  6.  int mycmp(const void* p1, const void* p2)  
  7.  {  
  8.      return strcmp(*(charconst*)p1, *(charconst*)p2);  
  9.  }  
  10.    
  11.  int getLen(char* p, char* q)  
  12.  {  
  13.      int ret = 0;  
  14.      while (*p && *p++ == *q++)  
  15.      {  
  16.          ++ret;  
  17.      }  
  18.      return ret;  
  19.  }  
  20.    
  21.  char* foo(char result[], char s[])  
  22.  {  
  23.      int len = strlen(s);  
  24.      char** suffix = new char*[len];  
  25.      for (int i = 0; i < len; ++i)  
  26.      {  
  27.          suffix[i] = s + i;  
  28.      }  
  29.      qsort(suffix, len, sizeof (char*), mycmp);  
  30.      //for (int i = 0; i < len; ++i)  
  31.      //{  
  32.      //    cout << suffix[i] << endl;  
  33.      //}  
  34.      int maxlen = 0, maxi = 0, maxj = 0, temp = 0;  
  35.      for (int i = 0; i < len - 1; ++i)  
  36.      {  
  37.          temp = getLen(suffix[i], suffix[i + 1]);  
  38.          if (temp > maxlen)  
  39.          {  
  40.              maxlen = temp;  
  41.              maxi = i;  
  42.              maxj = i + 1;  
  43.          }  
  44.      }  
  45.      //cout << maxlen << endl;  
  46.      //cout << suffix[maxi] << endl;  
  47.      //cout << suffix[maxj] << endl;  
  48.      //printf("%.*s\n", maxlen, suffix[maxi]);  
  49.      for (int i = 0; i < maxlen; ++i)  
  50.      {  
  51.          result[i] = suffix[maxi][i];  
  52.      }  
  53.      result[maxlen] = '\0';  
  54.      return result;  
  55.  }  
  56.    
  57.  int main()  
  58.  {  
  59.      char s[MAXN];  
  60.      char result[MAXN];  
  61.      while (cin >> s)  
  62.      {  
  63.          cout << foo(result, s) << endl;  
  64.      }  
  65.      return 0;  
  66. }  

 

╝③

§2最长不重复子串 

2.1问题描述

 

从一个字符串中找到一个连续子串,该子串中任何两个字符不能相同,求子串的最大长度并输出一条最长不重复子串。

下面介绍四种方法逐步优化,时间复杂度从O(n²)到O(n)。

 

2.2基本算法(使用hash)

 

要求子串中的字符不能重复,判重问题首先想到的就是hash,寻找满足要求的子串,最直接的方法就是遍历每个字符起始的子串,辅助hash,寻求最长的不重复子串,由于要遍历每个子串故复杂度为O(n^2),n为字符串的长度,辅助的空间为常数hash[256]。

 

C代码  收藏代码
  1. /* 最长不重复子串 设串不超过30 
  2.  * 我们记为 LNRS 
  3.  */  
  4. int maxlen;  
  5. int maxindex;  
  6. void output(char * arr);  
  7.    
  8. /* LNRS 基本算法 hash */  
  9. char visit[256];  
  10.    
  11. void LNRS_hash(char * arr, int size)  
  12. {  
  13.     int i, j;  
  14.     for(i = 0; i < size; ++i)  
  15.     {  
  16.         memset(visit,0,sizeof visit);  
  17.         visit[arr[i]] = 1;  
  18.         for(j = i+1; j < size; ++j)  
  19.         {  
  20.             if(visit[arr[j]] == 0)  
  21.             {  
  22.                 visit[arr[j]] = 1;  
  23.             }else  
  24.             {  
  25.                 if(j-i > maxlen)  
  26.                 {  
  27.                     maxlen = j - i;  
  28.                     maxindex = i;  
  29.                 }  
  30.                 break;  
  31.             }  
  32.         }  
  33.         if((j == size) && (j-i > maxlen))  
  34.         {  
  35.             maxlen = j - i;  
  36.             maxindex = i;  
  37.         }  
  38.     }  
  39.     output(arr);  
  40. }  

 

2.3动态规划求解

 

动态规划思想就是用于处理有重叠问题的求解,最大不重复子串一定是两个相同字符夹着的一段字符串加上这个字符,如abcac这里的最大不重复子串是a夹的一段。

当一个最长子串结束时(即遇到重复的字符),新的子串的长度是与第一个重复的字符的下标有关的,如果该下标在上一个最长子串起始位置之前,则dp[i] = dp[i-1] + 1,即上一个最长子串的起始位置也是当前最长子串的起始位置;如果该下标在上一个最长子串起始位置之后,则新的子串是从该下标之后开始的。简短几句话可能讲不是很明白,不过好在有程序可以帮助理解,惯例下面附上程序:

C代码  收藏代码
  1. /* LNRS dp */  
  2. int dp[30];  
  3.    
  4. void LNRS_dp(char * arr, int size)  
  5. {  
  6.     int i, j;  
  7.     int last_start = 0;     // 上一次最长子串的起始位置  
  8.     maxlen = maxindex = 0;  
  9.    
  10.     dp[0] = 1;  
  11.     for(i = 1; i < size; ++i)  
  12.     {  
  13.         for(j = i-1; j >= last_start; --j) // 遍历到上一次最长子串起始位置  
  14.         {  
  15.             if(arr[j] == arr[i])  
  16.             {  
  17.                 dp[i] = i - j;  
  18.                 last_start = j+1; // 更新last_start  
  19.                 break;  
  20.             }else if(j == last_start) // 无重复  
  21.             {  
  22.                 dp[i] = dp[i-1] + 1;  
  23.             }  
  24.         }  
  25.         if(dp[i] > maxlen)  
  26.         {  
  27.             maxlen = dp[i];  
  28.             maxindex = i + 1 - maxlen;  
  29.         }  
  30.     }  
  31.     output(arr);  
  32. }  

2.4动态规划+Hash求解

上面动态规划求解时间复杂度还是O(n²),主要是还是进行“回头”查找了重复元素位置,其实,上面并不是真正的动态规划方法,因为上面的求解过程没有记录有用的结果,所以可以通过记录之前出现的下标来改进算法,这样就不用每次都回去查找重复元素位置,这其实才是真正的动态规划方法,只是记录结果是用的Hash,这样的时间复杂度就是O(n)。

 

C代码  收藏代码
  1. /* LNRS dp + hash 记录下标 */  
  2. void LNRS_dp_hash(char * arr, int size)  
  3. {  
  4.     memset(visit, -1, sizeof visit);  
  5.     memset(dp, 0, sizeof dp);  
  6.     maxlen = maxindex = 0;  
  7.     dp[0] = 1;  
  8.     visit[arr[0]] = 0;  
  9.     int last_start = 0;  
  10.    
  11.     for(int i = 1; i < size; ++i)  
  12.     {  
  13.         if(visit[arr[i]] == -1)  
  14.         {  
  15.             dp[i] = dp[i-1] + 1;  
  16.             visit[arr[i]] = i; /* 记录字符下标 */  
  17.         }else  
  18.         {  
  19.             if(last_start <= visit[arr[i]])  
  20.             {  
  21.                 dp[i] = i - visit[arr[i]];  
  22.                 last_start = visit[arr[i]] + 1;  
  23.                 visit[arr[i]] = i; /* 更新最近重复位置 */  
  24.             }else  
  25.             {  
  26.                 dp[i] = dp[i-1] + 1;  
  27.             }  
  28.    
  29.         }  
  30.         if(dp[i] > maxlen)  
  31.         {  
  32.             maxlen = dp[i];  
  33.             maxindex = i + 1 - maxlen;  
  34.         }  
  35.     }  
  36.     output(arr);  
  37. }  

 

 进一步优化

上面的程序因为辅助的空间多了,是不是还能优化,仔细看DP最优子问题解的更新方程:

dp[i] = dp[i-1] + 1;

dp[i-1]不就是更新dp[i]当前的最优解么?这与最大子数组和问题的优化几乎同出一辙,我们不需要O(n)的辅助空间去存储子问题的最优解,而只需O(1)的空间就可以了,至此,我们找到了时间复杂度O(N),辅助空间为O(1)(一个额外变量与256大小的散列表)的算法,代码如下:

注意:当前最长子串的构成是与上一次最长子串相关的,故要记录上一次最长子串的起始位置!

 

C代码  收藏代码
  1. /* LNRS dp + hash 优化 */  
  2. void LNRS_dp_hash_impro(char * arr, int size)  
  3. {  
  4.     memset(visit, -1, sizeof visit);  
  5.     maxlen = maxindex = 0;  
  6.     visit[arr[0]] = 0;  
  7.     int curlen = 1;  
  8.     int last_start = 0;  
  9.    
  10.     for(int i = 1; i < size; ++i)  
  11.     {  
  12.         if(visit[arr[i]] == -1)  
  13.         {  
  14.             ++curlen;  
  15.             visit[arr[i]] = i; /* 记录字符下标 */  
  16.         }else  
  17.         {  
  18.             if(last_start <= visit[arr[i]])  
  19.             {  
  20.                 curlen = i - visit[arr[i]];  
  21.                 last_start = visit[arr[i]] + 1;  
  22.                 visit[arr[i]] = i; /* 更新最近重复位置 */  
  23.             }else  
  24.             {  
  25.                 ++curlen;  
  26.             }  
  27.         }  
  28.         if(curlen > maxlen)  
  29.         {  
  30.             maxlen = curlen;  
  31.             maxindex = i + 1 - maxlen;  
  32.         }  
  33.     }  
  34.     output(arr);  
  35. }  

 

 最后在给出测试用例

 

C代码  收藏代码
  1. /* 输出LNRS */  
  2. void output(char * arr)  
  3. {  
  4.     if(maxlen == 0)  
  5.     {  
  6.         printf("NO LNRS\n");  
  7.     }  
  8.     printf("The len of LNRS is %d\n",maxlen);  
  9.    
  10.     int i = maxindex;  
  11.     while(maxlen--)  
  12.     {  
  13.         printf("%c",arr[i++]);  
  14.     }  
  15.     printf("\n");  
  16. }  
  17.    
  18. void main()  
  19. {  
  20.      char arr[] = "abcaacdeabacdefg";  
  21.    
  22.      /* LNRS 基本算法 */  
  23.      LNRS_hash(arr,strlen(arr));  
  24.    
  25.      /* LNRS dp */  
  26.      LNRS_dp(arr,strlen(arr));  
  27.    
  28.      /* LNRS dp + hash 记录下标 */  
  29.      LNRS_dp_hash(arr,strlen(arr));  
  30.    
  31.      /* LNRS dp + hash 优化方案 */  
  32.      LNRS_dp_hash_impro(arr,strlen(arr));  
  33. }  

 ╝④

 

§3 小结

这篇文章把字符串最长重复子串和最长不重复子串的求解方法,能有了一定的认识和理解,基本可以掌握这些方法。如果你有任何建议或者批评和补充,请留言指出,不胜感激,更多参考请移步互联网。

 

 

参考:

勇幸|Thinking: http://www.ahathinking.com/archives/121.html

②huang12315: http://blog.csdn.net/huang12315/article/details/6455090

③unixfy: http://www.cppblog.com/unixfy/archive/2011/05/23/146986.html

勇幸|Thinking: http://www.ahathinking.com/archives/123.html

 


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/450174.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前后端分离——token超时刷新策略

前言 记录一下前后端分离下————token超时刷新策略&#xff01; 需求场景 昨天发了一篇记录 前后端分离应用——用户信息传递 中介绍了token认证机制&#xff0c;跟几位群友讨论了下&#xff0c;有些同学有这么一个疑惑&#xff1a;token失效了&#xff0c;应该怎么做&…

阿里云@广东:让我们一起上云!

锅炉故障预测 预警准确率95%以上 利用算法模型进行制程能力的 综合分析与评估优化 提升定标效率 时间降低30% 构建垃圾焚烧发电工艺的优化算法模型 综合考虑环保和设备负荷因素 推荐最优工艺参数组合 优化人工操作&#xff0c;提高燃烧效率 中海油能源发展公司 石油钻探预测优化…

weblogic jprofile配置

前提&#xff1a; 1.安装好weblogic 2.安装好jprofile 非等待模式&#xff1a; export JAVA_OPTIONS"${JAVA_OPTIONS} -Dweblogic.threadpool.MinPoolSize100 -Dweblogic.threadpool.MaxPoolSize1000 -Djava.awt.headlesstrue -agentpath:/opt/jprofiler9/bin/linux-x64/l…

音视频引擎研究

音视频包&#xff1a;http://ishare.iask.sina.com.cn/f/33851582.html 1、WebRTC目的 WebRTC&#xff08;Web Real-Time Communication&#xff09;项目的最终目的主要是让Web开发者能够基于浏览器&#xff08;Chrome\FireFox\...&#xff09;轻易快捷开发出丰富的实时多媒体应…

潭州课堂25班:Ph201805201 django 项目 第三十九课 后台 文章发布,图片上传到 FastDFS后端实现 七牛云讲解(课堂笔记)...

文章发布&#xff1a; # 1&#xff0c;从前台获取参数# 2&#xff0c;校验参数# 3&#xff0c;把数据保存到数据库# 4&#xff0c;返回执行结果到前台&#xff0c;&#xff08;创建成功或失败&#xff09;自定义 froms.py 校验参数 上传图片到七牛云 注册 https://www.qiniu.c…

原来公司需要这样的你

担任项目经理也有几年的时间了&#xff0c;项目组里来了不少的刚毕业或者工作时间不长的年轻人&#xff0c;有精明能干的&#xff0c;有中庸无为的也有自暴自弃混日子的&#xff0c;但再优秀的年轻人也会犯这样那样的错误&#xff0c;我总结起来一般就是以下这些问题&#xff0…

git使用—rebase还是merge

转载自&#xff1a;https://segmentfault.com/q/1010000007704573/ 我猜现实中的情况是这样的&#xff1a; 使用 git 的人群中&#xff0c;不会用 rebase&#xff08;哪怕是基础功能的&#xff09;的至少一半&#xff08;这个估计恐怕很保守了&#xff09; 剩下一半里真正理解…

淘宝网轮播图

转载于:https://www.cnblogs.com/wxwxwx/p/10264370.html

atob和btoa的趣谈

2019独角兽企业重金招聘Python工程师标准>>> 不了解的人突然看到window对象的atob和btoa 函数&#xff0c;估计会认为哪个臭小子添加全局函数了。 你如果告诉他这是原生函数&#xff0c;他一定会怒骂&#xff1a;哪个脑残给api起个这样的名子。 你能猜出来这两个函数…

esp32使用lvgl,给图片取模显示图片

使用LVGL官方工具。 https://lvgl.io/tools/imageconverter 上传图片&#xff0c;如果想要透明效果&#xff0c;那么选择 输出格式C array&#xff0c;点击Convert进行转换。 下载.c文件放置到工程下使用即可。

ASM 判定一个类,实现了指定接口

为什么80%的码农都做不了架构师&#xff1f;>>> ASM 判定一个类&#xff0c;实现了指定接口 技术支持 ASM 中&#xff0c;ClassReader 类&#xff1a;对已存在的进行解析&#xff0c;并提供获取类信息的方法。 通过 ClassReader &#xff0c;实现对一个类的解析。 …

exgcd模板

逆元模板P1082 1 #include <cstdio>2 #include <algorithm>3 4 int exgcd(int a, int b, int &x, int &y) {5 if(!b) {6 x 1;7 y 0;8 return a;9 } 10 int g exgcd(b, a % b, x, y); 11 std::swap(x, y); 12 …

ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案

ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案 原文:ASP.NET Core 2.2 : 十六.扒一扒2.2版更新的新路由方案ASP.NET Core 从2.2版本开始&#xff0c;采用了一个新的名为Endpoint的路由方案&#xff0c;与原来的方案在使用上差别不大&#xff0c;但从内部运行方式上来说…

用jenkins创建节点

原料&#xff1a;(1)jre下载链接&#xff1a;https://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html (2)jdk:下载链接&#xff1a;https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 一、创建SLAVE节点…

解决 Script Error 的另类思路

2019独角兽企业重金招聘Python工程师标准>>> 本文由小芭乐发表 前端的同学如果用 window.onerror 事件做过监控&#xff0c;应该知道&#xff0c;跨域的脚本会给出 "Script Error." 提示&#xff0c;拿不到具体的错误信息和堆栈信息。 这里读者可以跟我一…

迅雷影音怎样 1.5倍速度播放

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 看视频 觉得播放速度太慢&#xff0c;想让1.5速度播放可以这样设置&#xff1a; 点击快进按钮&#xff0c;点一次变为1.1倍&#xff0c…

git pull时冲突的几种解决方式

仅结合本人使用场景&#xff0c;方法可能不是最优的 1. 忽略本地修改&#xff0c;强制拉取远程到本地 主要是项目中的文档目录&#xff0c;看的时候可能多了些标注&#xff0c;现在远程文档更新&#xff0c;本地的版本已无用&#xff0c;可以强拉 git fetch --allgit reset --h…

Linux:echo命令详解

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 echo命令 用于字符串的输出 格式 echo string使用echo实现更复杂的输出格式控制 1.显示普通字符串: echo "It is a test"这里…

学习 shell脚本之前的基础知识

见 : http://www.92csz.com/study/linux/12.htm【什么是shell】 简单点理解&#xff0c;就是系统跟计算机硬件交互时使用的中间介质&#xff0c;它只是系统的一个工具。实际上&#xff0c;在shell和计算机硬件之间还有一层东西那就是系统内核了。打个比方&#xff0c;如果把计算…

Git cherry-pick后再merge出现一个“奇怪”的现象

背景描述&#xff1a;有的时候基于一个master branch拉出一个独立feature分支做开发时&#xff0c;两条分支都在并行开发&#xff0c;如果master分支增加了某些功能&#xff0c;解决了某些关键bug&#xff0c;而独立feature分支不需要所有的增加的commit&#xff0c;只需要某一…