最字头之二:最长公共字串

最长公共子串(Longest Common Substring, LCS) 题目最长公共子串类似最长公共子序列,但是要搞清一个问题就是公共子串要求字符必须连续。例如:字符串13455与145576的最长公共子序列为1455而最长公共子串为455解析 使用二维数组c[m][n] c[i][j]记录序列X和Y的最长公共子串的长度 实现public String LongCommonSubstring(String A, String B){ int[][] dp 阅读详情
除代码外,其他文字部分均为直接转载。代码为参考。
文章作者: Yx.Ac   文章来源: 勇幸|Thinking ( http://www.ahathinking.com)   转载请注明,谢谢合作。

---

这个LCS跟前面说的最长公共子序列的LCS不一样,不过也算是LCS的一个变体,在LCS中,子序列是不必要求连续的,而子串则是“连续”的。即:

题:给定两个字符串X,Y,求二者最长的公共子串,例如X=[aaaba],Y=[abaa]。二者的最长公共子串为[aba],长度为3。

本节给出三种不同的实现方式,并对比分析每种方法的复杂度,内容如下:

==基本算法==

==DP方案==

==后缀数组==

==各方法复杂度分析==

==================================

基本算法

其实对于最长公共子串,还是比较简单易想的,因为子串是连续的,这就方便了很多。最直接的方法就是用X每个子串与Y的每个子串做对比,求出最长的公共子串。代码如下:

#include <iostream>
#include <string>
using namespace std;

int tmplen(char* x,char* y){
  int len = 0;
  while(*x && *y && *x++ == *y++){//注意边界值的可能性
    len++;
  }
  return len;
}
int main(){
	string s1,s2;
	int maxlen = 0;
	int index = 0;
	cin >> s1 >> s2;
	for(int i = 0; i < s1.size(); i++){
	   for(int j = 0; j < s2.size(); j++){
	      int len = tmplen(&s1[i],&s2[j]);
		  if(len > maxlen){
		     maxlen = len;
			 index = i;
		  }
	   }
	}
	for(int i = index; i < index+maxlen; i++){
	  cout << s1[i] << " ";
	}
	cout << endl;
	return 0;
}

==================================

DP方案

既然最长公共子串是最长公共子序列的变体,那么最长公共子串是不是也可以用动态规划来求解呢?

我们还是像之前一样“从后向前”考虑是否能分解这个问题,在最大子数组和中,我们也说过,对于数组问题,可以考虑“如何将arr[0,...i]的问题转为求解arr[0,...i-1]的问题”,类似最长公共子序列的分析,这里,我们使用dp[i][j]表示 以x[i]和y[j]结尾的最长公共子串的长度,因为要求子串连续,所以对于X[i]与Y[j]来讲,它们要么与之前的公共子串构成新的公共子串;要么就是不构成公共子串。故状态转移方程

  1. X[i] == Y[j],dp[i][j] = dp[i-1][j-1] + 1
  2. X[i] != Y[j],dp[i][j] = 0

对于初始化,i==0或者j==0,如果X[i] == Y[j],dp[i][j] = 1;否则dp[i][j] = 0。

代码如下:

#include <iostream>
#include <string>
using namespace std;

int main(){
  string s1,s2;
  int index = 0;
  int maxlen = 0;
  int dp[30][30] = {0};
  cin >> s1 >> s2;
  for(int i = 0; i < s1.size(); i++){
     for(int j = 0; j < s2.size(); j++){
	    if(s1[i] == s2[j]){
		   if(i == 0|| j == 0){
		     dp[i][j] = 1;
		   }else{
		     dp[i][j] = dp[i-1][j-1] + 1;
		   }
		   if(dp[i][j] > maxlen){
		      maxlen = dp[i][j];
			  index = i;
		   }
		}
	 }
  }
  for(int i = index - maxlen + 1; i <= index; i++){
    cout << s1[i] << " ";
  }
  cout << endl;
  return 0;
}

==================================

后缀数组

前面提过后缀数组的基本定义,与子串有关,可以尝试这方面思路。由于后缀数组最典型的是寻找一个字符串的重复子串,所以,对于两个字符串,我们可以将其连接到一起,如果某一个子串s是它们的公共子串,则s一定会在连接后字符串后缀数组中出现两次,这样就将最长公共子串转成最长重复子串的问题了,这里的后缀数组我们使用基本的实现方式。

值得一提的是,在找到两个重复子串时,不一定就是X与Y的公共子串,也可能是X或Y的自身重复子串,故在连接时候我们在X后面插入一个特殊字符‘#’,即连接后为X#Y。这样一来,只有找到的两个重复子串恰好有一个在#的前面,这两个重复子串才是X与Y的公共子串。

代码如下:

/* 最长公共子串 后缀数组 */
char * suff[100];
 
int pstrcmp(const void *p, const void *q)
{
     return strcmp(*(char**)p,*(char**)q);
}
 
int comlen_suff(char * p, char * q)
{
     int len = 0;
     while(*p && *q && *p++ == *q++)
     {
         ++len;
         if(*p == '#' || *q == '#')
         {
             break;
         }
     }
     int count = 0;
     while(*p)
     {
         if(*p++ == '#')
         {
             ++count;
             break;
         }
     }
     while(*q)
     {
         if(*q++ == '#')
         {
             ++count;
             break;
         }
     }
     if(count == 1)
         return len;
     return 0;
}
 
void LCS_suffix(char * X, int xlen, char * Y, int ylen)
{
     int suf_index = maxlen = maxindex = 0;
 
     int len_suff = xlen + ylen + 1;
     char * arr = new char [len_suff + 1];  /* 将X和Y连接到一起 */
     strcpy(arr,X);
     arr[xlen] = '#';
     strcpy(arr + xlen + 1, Y);
 
     for(int i = 0; i < len_suff; ++i)  /* 初始化后缀数组 */
     {
         suff[i] = & arr[i];
     }
 
     qsort(suff, len_suff, sizeof(char *), pstrcmp);
 
     for(int i = 0; i < len_suff-1; ++i)
     {
         int len = comlen_suff(suff[i],suff[i+1]);
         if(len > maxlen)
         {
             maxlen = len;
             suf_index = i;
         }
     }
     outputLCS(suff[suf_index]);
}

【可以参考之前的博客文章:后缀树】

各方案复杂度对比

设字符串X的长度为m,Y的长度为n,最长公共子串长度为l。

对于基本算法,X的子串(m个)和Y的子串(n个)一一对比,最坏情况下,复杂度为O(m*n*l),空间复杂度为O(1)。

对于DP算法,由于自底向上构建最优子问题的解,时间复杂度为O(m*n);空间复杂度为O(m*n),当然这里是可以使用滚动数组来优化空间的,滚动数组在动态规划基础回顾中多次提到。

对于后缀数组方法,连接到一起并初始化后缀数组的时间复杂度为O(m+n),对后缀数组的字符串排序,由于后缀数组有m+n个后缀子串,子串间比较,故复杂度为O((m+n)*l*lg(m+n)),求得最长子串遍历后缀数组,复杂度为O(m+n),所以总的时间复杂度为O((m+n)*l*lg(m+n)),空间复杂度为O(m+n)。

总的来说使用后缀数组对数据做一些“预处理”,在效率上还是能提升不少的。




序列和串(数组)相关 子序列和子串的区别: 子串要求连续,子序列则不要求连续,只要求相对顺序与原序列相同 单个字符串/序列问题 #最长递增子序列问题(力扣300) (1)动态规划(O(N^2)) 用一个额外数组存储,以 当前索引对应的值 作为结尾 的 子序列中,长度最大的值 例: 对于序列 10,9,2,5,3,7,101,18 len数组 1,1,1,2,2,3, 4, 4 遍历至len数组i位置时,将nums[i]的值与nums的0到i - 1的值比较,若大于其值, 则可作为候选序列与当前最大序列长度比较,最终得到最大值 阅读详情

相关推荐

最长公共连续子串(Longest Common Substring)

上一篇博客,介绍了最长公共子序列(Longest Common Sequence, LCS),本文介绍最长公共连续子串。与序列不同,子串要求字符是连续的,而子序列可以不连续。下面同样用LCS表示最长公共连续子串。首先分析一下,如果暴力求解法的时间复杂度,两个字符串A和B长度分别为x和y,则字符串的子串个数分别为n1 = x + (x-1) + ... + 1 = x(x-1) / 2n2 = y ...

tensory的博客 1万+

后缀数组的应用:最长公共子串

用O(N+M)的复杂度解决最长公共子串问题

明朗晨光的专栏 1702

液晶电视维修必看:TCON逻辑板故障排查全指南(附常见问题解决方案)

本文详细解析了液晶电视TCON逻辑板的工作原理与故障排查方法,涵盖LVDS信号处理、DC-DC电压系统等核心知识。提供从基础测量到高级维修的完整指南,帮助技术人员快速诊断黑屏、竖线等常见故障,并给出预防性维护建议,提升维修效率与成功率。

weixin_42535461的博客 616

LCP poj 2217 寻找最长公共子串

题目:http://poj.org/problem?id=2217 首先解释,DP中的最长公共子序列和此处的最长公共子串区别-------------------序列可以是不连续的,但是子串是连续的 其次,LCP,lcp[i]就是lcp[rank[i]]和lcp[rank[i]+1]的最长公共前缀,那么把两个字符串接起来,然后找最长的lcp,就是答案 思路还是比较清晰的 上

Pilgrim 2865

OpenClaw Browser Relay Extension 鉴权失败与 invalid handshake 问题的终极解决方案

在使用 OpenClaw 浏览器插件 (Browser Relay Extension) 时,许多用户可能会遇到两个主要问题:一是配置端口错误导致的连接失败,二是鉴权失败 (Gateway token rejected)。本文将详细解析这两个问题并提供解决方案。

gerryke的blog 5559

Drools vs URule:5个真实项目场景告诉你如何选择开源规则引擎

本文通过电商风控、物流计费、会员体系、保险定价和物联网告警五个真实项目场景,深度对比了Drools与URule两款开源规则引擎。分析了两者在规则复杂度、性能表现、开发运维及适用场景上的核心差异,为技术选型提供基于实战的决策指南。

k6l7m8n9的博客 944

后缀数组——处理字符串的有力工具

<br />后缀数组——处理字符串的有力工具<br />作者:罗穗骞<br />2009年1月<br />【摘要】<br />  后缀数组是处理字符串的有力工具。后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也并不逊色,而且它比后缀树所占用的内存空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。本文分两部分。第一部分介绍两种构造后缀数组的方法,重点介绍如何用简洁高效的代码实现,并对两种算法进行了比较。第二部分介绍后缀数组在各种类型题目中的具体

岩山放牛娃 2969

Jetson Nano官方镜像下载&烧录

在NVIDIA Jetson Nano开发者套件上安装NVIDIA官方Ubuntu镜像

weixin_44222088的博客 8851

动态规划-最长公共子串(c)

动态规划(dynamic programming)是一种算法设计方法。基本思想是在对一个问题的多阶段决策中,按照某一顺序,根据每一步所选决策的不同,会引起状态的转移,最后会在变化的状态中获取到一个决策序列。上面这段话是比较官方的术语描述,还可以这样从编程层面理解:动态规划一般用于求解具有重叠子问题和最优子结构特性的问题。它通过将大问题分解为小问题,并存储小问题的解(通常在一个表格中),避免了重复计算,从而提高了效率。动态规划可以应用于许多类型的问题,包括但不限于最优化问题、计数问题和决策问题。

Mr.H的专栏 957

Python自动化办公:5分钟搞定钉钉群文件自动发送(附完整代码)

本文详细介绍了如何利用Python实现钉钉群文件的自动化发送。通过结合钉钉企业内部应用与群机器人API,构建了包含智能令牌管理、文件上传与消息发送的完整工作流。文章提供了可直接复用的完整代码,并分享了定时任务、目录监听等进阶实战方案,帮助职场人士高效解决重复性文件发送难题,实现办公自动化。

7z8x2c4v6b9的博客 325

【NLP】第十章:位置编码Positional Encoding

前面讲RNN时一般都会有一个ht的输出,这个输出就是样本与样本之间的信息,而且RNN是时序循环的,所以RNN本身的结构,在物理上,就自带词的前后顺序信息的。比如词never,不管never本身的词向量是什么,只要你在序列中排第一个位置,你的位置向量就是[0,1,01],你排第三个位置,你的位置向量就是[0.9,-0,4,0.2,1.0]。也所以,你可以理解为:embedding后的词嵌入,PE按照每个样本在sequence中的位置,把embedding后的词嵌入给扭转后,才送入attention的。

因为热爱所以坚持 1918

夕拾算法进阶篇:21)最长公共子串(DP+后缀数组)

动态规划 这里首先区分最长最长公共子序列和最长公共子串,子串要求是连续的,而序列不要求,在之前的博客中讨论过如果用DP来解决最长公共序列的问题,子串和子序列的解决方法类似。 令二维数组dp[i][j]表示字符串A的i号位和字符串B的j号位之前的最长公共子序列,如dp[4][6]表示“sads”和“admins”的最长公共子序列长度。那么A[i]和B[j]可以分为2种情况: (1)A[i]

Javis486的专栏 711

【AI Agent 记忆共享实战】【下篇】多 Agent 会话 Session 统一共享实战:全链路工程落地、采集归一、MCP 服务、私有化部署、源码级实战

中篇我们已经锁定最终最优架构方案: 非侵入式、外部旁路采集、只读归一、中心化存储、MCP 按需检索注入。摒弃「改 Agent 源码」「软链接互通」「原生 Session 互导」等不现实方案。本篇直接落地可上线、可简历复用、可个人私有化部署多 Agent 异构 Session专属采集器(Claude/CODex/DSH/OpenCode/Kimi/WorkBuddy/ 豆包)统一消息归一化解析层(解决所有 Schema 异构)增量同步、去重、时间戳对齐核心逻辑。

十年老程序猿,AI 疯狂研究专家,前沿硬核知识分享 482

Longest Common Substring(最长公共子序列)

Longest Common Substring Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 37 Accepted Submission(s): 28 Problem D...

weixin_34072637的博客 176

探索CompletableFuture:高效异步编程的利器

随着现代软件系统的复杂性和用户需求的多样化,异步编程成为了提升系统性能和响应速度的重要手段。在Java领域,CompletableFuture作为Java 8引入的新特性,提供了强大的异步编程能力,极大地简化了多线程和并发任务的处理。本文将深入探讨CompletableFuture的基本功能和使用方法,介绍如何利用其提升程序的并发性能和代码的可维护性。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 170万+

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

数据结构学习——串

一、串的基本定义 串是内容受限的线性表(数据元素只能是字符),是由零个或多个任意字符组成的有限 子串:一个串中任意个连续字符组成的子序列(含空串),另外真子串是指不包含自身的所有子串 主串:包含子串的串 字符位置:字符在序列中的序号 子串位置:子串第一个字符在主串中的位置 空格串:由一个或多个空格组成的串 串相等:两个串长度相等并且各个对应位置的字符都相同 二、串的存储结构 串中元素的逻辑关系与线性表的相同,因此可以采用与线性表相同的存储结构,顺序存储结构的叫顺序串,链式存储结构的叫链..

weixin_55653836的博客 2678

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

算法:字符串全部子序列、子串、全排列

我们先来看看字符串的子串和子序列有什么区别? 字符串的子串:必须是连续的一段 字符串的子序列:可以不连续,但是相对次序不能乱(每个字符 要跟不要 所有的路都走一遍)——深度优先遍历 打印出字符串的全部子串 #include <string> #include <vector> using namespace std; void helper(std::string str, std::vector<std::string> & ans){ for(i

OceanStar的博客 8772

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

最长公共子串 Longest-Common-Substring(LCS)

给定两个字符串X,Y,求二者最长的公共连续子串,如 X = [abcdefg],Y = [bacdegf],二者的最长公共连续子串是 cde 长度为3, 这里讨论 DP 方案。     考虑 m = X.length,n = Y.length 开辟一个大小为 m x n 的数组 d[m][n],使用 d[i][j] 表示以 X[i] 与 Y[j] 结尾的最长公共子串的长度,因为要求子串连续,所

zhangpiu的专栏 876

求两个字符串的最长公共子字符串长度

题目描述 给定两个字符串,请编写代码,输出最长公共子串(Longest Common Substring),是指两个字符串中的最长的公共子串,要求子串一定是连续。 输入描述: 文本格式,2个非空字符串(字母数字组成),2个字符串以","英文逗号分割,字符串长度均小于等于100。 输出描述: 整形,为匹配到的最长子串长度 示例1 输入 复制 bab,caba 输出 复制 ...

qq_35747066的博客 1617

子串和子序列

算法题1. 最大子串和 1. 最大子串和   输入整数序列,输出所有连续子串中和最大的那个子串的和。   示例输入:[1,2,3,−4,5,−6][1, 2, 3, -4, 5, -6][1,2,3,−4,5,−6],输出777。 // 1.暴力法:遍历所有子串。 template<size_t N> int maxSubStr1(int (&arr)[N]) { int Max = INT_MIN; for (int i = 0; i < N; i++) { int SU

sinoai 1505

最长公共子串(后缀数组)

#include #include #include #include #include #include #include               //POJ2774 最长公共子串(后缀数组) #define maxn 200005 using namespace std; int wa[maxn],wb[maxn],wv[maxn],Ws[maxn], Ra

u012432475的专栏 911

后缀数组以及利用后缀数组求取最长公共字串

后缀树组是一个字符串的所有后缀的排序数组。后缀是指从某个位置 i 开始到整个串末尾结束的一个子串。字符串 r 的从 第 i 个字符开始的后缀表示为 Suffix(i) ,也就是Suffix(i)=r[i..len(r)] 。如下图所示: 对于字符串banana,所有的后缀字符串是banana,anana,nana,ana,na,a。我们对其进行排序得到右边的排序序列,其相应的索引值这时候就构成

Jack Lee 1860

后缀数组之最长公共子串 poj 2774

用后缀数组求两个串的最长公共子串的长度 详见罗穗骞的论文 #include &lt;iostream&gt; #include &lt;cstdio&gt; #include &lt;cstring&gt; #include &lt;string&gt; #include &lt;algorithm&gt; #include &lt;cstdlib&gt; #define MAXN 1000...

java开发指南博客 【转载】 185

poj 2774 最长公共子串--字符串hash或者后缀数组或者后缀自动机

http://poj.org/problem?id=2774 想用后缀数组的看这里:http://blog.youkuaiyun.com/u011026968/article/details/22801015 本文主要讲下怎么hash去找 开始的时候写的是O(n^2 logn)算法 果断超时。。。虽然也用了二分的,, 代码如下: //hash+二分 #include #include #incl

Pilgrim 2810

POJ 2774 后缀数组:求最长公共子串

思路:其实很简单,就是两个字符串连接起来,中间用个特殊字符隔开,然后用后缀数组求最长公共前缀,然后不同在两个串中,并且最长的就是最长公共子串了。 注意的是:用第一个字符串来判断是不是在同一个字符中,刚开始用了第二个字符的长度来判断WA了2发才发现。 #include #include #include #include #include #include #include #include #

liwei的专栏 1062

poj 2774 最长公共子串(后缀数组)

题目:http://poj.org/problem?id=2774 题意: 给定两个字符串 A 和 B,求最长公共子串。分析: 字符串的任何一个子串都是这个字符串的某个后缀的前缀。求 A 和 B 的最长公共子串等价于求 A 的后缀和 B 的后缀的最长公共前缀的最大值。如果枚举 A和 B 的所有的缀,那么这样做显然效率低下。由于要计算 A 的后缀和 B 的后缀的最长公共前缀,所以先第二个字符串

HARD_UNDERSTAND 577
上一篇: 最字头之一:最长公共子序列
下一篇: 最字头之三:最长重复子串
louis9212
博客等级 码龄14年 8粉丝 49原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值