假设以定长顺序存储结构表示串,设计一个算法: void maxcommonstr(SString s, SString t, int &maxlen, int &pos1, int &pos2); 求串 s 和串 t 的一个最长公共子串,其中带出参数 maxlen 为最长公共子串的长度,pos1 和 pos2 分别为最长公共子串在串 s 和串 t 中的起始位置
时间: 2025-07-13 22:40:46 浏览: 6
<think>嗯,用户让我设计一个算法,用定长顺序存储结构表示串,然后找出两个串s和t的最长公共子串,并返回长度和起始位置。首先,我需要明确定长顺序存储结构的特点,比如每个串可能有固定长度,用数组存储,可能用某个字符表示结束或者有长度字段。然后,最长公共子串的问题,和最长公共子序列不同,子串必须是连续的。
那首先,我得回忆一下常见的解决方法。通常,动态规划(DP)是解决这类问题的有效方法。比如,用一个二维数组dp[i][j]表示s的前i个字符和t的前j个字符的最长公共子串长度,且这个子串以s的第i个字符和t的第j个字符结尾。这样,当s[i] == t[j]时,dp[i][j] = dp[i-1][j-1] +1,否则dp[i][j]=0。在这个过程中,记录下最大的长度以及对应的位置。
不过,这样的时间复杂度是O(mn),其中m和n是两个串的长度。如果两个串都很长的话,可能会比较耗时。但用户这里没有提到时间效率的要求,所以可能这种方法是可以接受的。
接下来,我需要考虑如何将动态规划的方法应用到定长顺序存储结构上。定长顺序存储结构可能意味着每个串都有一个固定的最大长度,比如用数组存储,每个字符依次存放,可能有一个长度字段或者以特定字符结尾。例如,SString可能定义为一个结构体,包含一个字符数组和一个整型长度。
那么,在具体实现的时候,我需要遍历s和t的每个可能的起始位置,然后比较对应的字符是否相同,并计算连续相同的长度。或者,按照动态规划的思路,创建一个二维数组来保存中间结果。
不过,动态规划的二维数组的空间复杂度是O(mn),如果m和n很大,可能会占用较多内存。有没有优化的方法?比如,可以只使用一维数组,因为在计算dp[i][j]时,只需要前一行的dp[i-1][j-1]的值。这样可以降低空间复杂度到O(n)或O(m),取决于实现方式。
但用户可能更关注正确性,所以先用标准的动态规划方法实现,确保正确后再考虑优化。
那么具体步骤大概是:
1. 初始化一个二维数组dp,维度为(s.length +1) x (t.length +1),初始化为0。
2. 遍历i从1到s的长度,遍历j从1到t的长度:
a. 如果s[i-1] == t[j-1],则dp[i][j] = dp[i-1][j-1] +1。
b. 否则,dp[i][j] =0。
c. 在每次更新dp[i][j]时,检查是否大于当前记录的maxlen,如果是,则更新maxlen,并记录此时的i和j的位置。
3. 最后,根据记录的i和j的位置,计算在s和t中的起始位置pos1和pos2。例如,pos1 = i - maxlen(因为子串的长度是maxlen,所以起始位置是当前i减去maxlen的位置)。类似地,pos2 = j - maxlen。
这里需要注意的是,字符串的索引是否正确。比如,假设s和t都是0-based的数组,那么i和j在循环中是从1到len,而对应的字符位置是i-1和j-1。同时,当找到更大的maxlen时,需要记录当前的i和j,这样才能计算出起始位置。
举个例子,假设s是"abcde",t是"aabc",当i=3(对应字符'c'),j=3(对应字符'b')时,如果此时s[i-1]和t[j-1]不相等,那么dp[i][j]=0。而当i=2(字符'b'),j=2(字符'a')时,可能相等的话,dp值会增加。
比如,当s和t有一段连续相同的字符,比如s的子串从i=2到i=4,长度3,对应到maxlen=3,此时i是4,那么起始位置应该是4 -3 +1?或者i - maxlen?
比如,假设i是当前的位置,且最长长度为maxlen,则起始位置应为i - maxlen的位置。例如,当s[i-1]是当前字符,而连续的长度是maxlen,那么起始位置是i - maxlen的位置。例如,在字符串数组从0开始的情况下,假设i是s的当前索引+1(因为dp数组的维度是len+1),那么起始位置应该是i - maxlen的位置。
比如,s是"ababc",当最长公共子串是"bab",长度3,出现在s的索引1到3的位置,那么i在dp数组中对应的是3的位置,i-maxlen=0?这似乎有问题。或者可能我搞反了索引?
可能这个问题需要更仔细的计算。假设在动态规划中,dp[i][j]表示的是以s的第i个字符和t的第j个字符结尾的最长公共子串的长度。例如,当s的第i个字符和t的第j个字符相同,那么该子串的长度是dp[i-1][j-1]+1。因此,当这个长度大于当前maxlen时,该子串在s中的起始位置是i - maxlen(假设新的maxlen是dp[i][j]),而原字符串的索引是从0开始的,所以s中的起始位置是i - maxlen(因为i是当前的位置索引+1?比如,假设s的索引是0到len-1,那么dp数组的i从1到len,对应的字符是s[i-1]。因此,当最长子串的结尾位置是i-1(原字符串中的位置),那么起始位置是 (i-1) - (maxlen -1) = i - maxlen。例如,长度是3,结尾在i=4(s[3]),则起始位置是4 -3 +0 =1?或者可能需要具体例子验证。
比如,假设s是 "abcde",最长子串是 "bcd",那么在s中的起始位置是1(索引从0开始),长度是3。那么对应的i在dp数组中是4(因为s[3]是 'd',i=4)。此时maxlen是3,则起始位置是4 -3 =1,正确。所以,pos1应为 i - maxlen。同理,pos2是j - maxlen。所以,在记录maxlen时,需要同时记录当前的i和j,然后计算pos1 = i - maxlen,pos2 = j - maxlen。但要注意的是,这里的i和j是dp数组中的索引,对应原字符串中的位置是i-1和j-1?或者原字符串的索引?
比如,假设s的长度是m,那么在循环中,i的取值范围是1到m,对应的s中的字符是s.ch[i-1],假设SString的结构体有一个ch数组,从0开始存储字符。所以,当i=1,对应s.ch[0],即第一个字符。此时,当最长子串的结尾在i的位置,那么原字符串中的位置是i-1。起始位置pos1应该是 (i-1) - (maxlen -1) = i - maxlen。例如,当maxlen是3,i-1是3(即第四个字符),那么起始位置是3 -3 +1 =1?或者可能我的计算有误。
或者,更简单来说,如果当前子串的长度是maxlen,结尾在i-1的位置(原字符串中的索引),那么起始位置是 (i-1) - (maxlen-1) = i - maxlen。例如,maxlen=3,结尾在i-1=3(索引3),则起始位置是3 -3 +1 =1?或者应该是i-1 - (maxlen-1) =3 -2=1,是的。所以,起始位置是i - maxlen的位置。比如,i是dp数组中的索引,对应原字符串中的i-1的位置。那么起始位置pos1是 (i-1) - (maxlen -1) = i - maxlen。所以,在代码中,当maxlen被更新时,记录当前的i和j,然后pos1 = i - maxlen,pos2 = j - maxlen。
但需要确保i和j的索引是否正确。例如,当dp[i][j]的值是maxlen,这时候i对应的s的位置是i-1,所以起始位置是 (i-1) - (maxlen-1) = i - maxlen。是的。因此,在代码中,当找到更大的maxlen时,保存当前的i和j的值,然后计算pos1和pos2的时候用i - maxlen和j - maxlen。
那现在,如何初始化这个二维数组?对于动态规划来说,通常的初始化是dp[0][j]和dp[i][0]都为0,因为其中一个字符串长度为0时,公共子串长度为0。这已经在数组初始化的时候处理了。
接下来,代码的结构大概是这样的:
初始化maxlen为0,pos1和pos2为0。
创建二维数组dp[m+1][n+1],其中m和n是s和t的长度。初始化为0。
然后循环i从1到m,j从1到n:
如果s.ch[i-1] == t.ch[j-1],那么dp[i][j] = dp[i-1][j-1] +1;
否则,dp[i][j] =0;
如果dp[i][j] > maxlen,则更新maxlen,并记录当前的i和j的值,作为结束的位置。
最后,计算pos1 = i - maxlen,pos2 = j - maxlen。
但这里需要注意到,在每次发现更大的maxlen时,必须同时记录当前的i和j,否则最后记录的i和j可能不是对应的那个情况。例如,可能有多个相同长度的子串,但题目只需要一个,所以可以记录第一个遇到的或者最后一个。题目没有说明要哪一个,所以可以任选其一。
那在代码中,每次当dp[i][j] > maxlen时,才更新maxlen,并记录当前的i和j。例如:
初始化maxlen=0,pos1=0,pos2=0。
遍历i从1到m:
遍历j从1到n:
如果s.ch[i-1] == t.ch[j-1]:
dp[i][j] = dp[i-1][j-1] +1
如果 dp[i][j] > maxlen:
maxlen = dp[i][j]
pos1_end = i-1 // 原字符串中的结束位置
pos2_end = j-1
否则:
dp[i][j] =0
最后,pos1 = pos1_end - maxlen +1 ?
或者,原字符串的起始位置是 pos1_end - maxlen +1?例如,如果结束位置是i-1,长度为maxlen,那么起始位置是 (i-1) - (maxlen -1) = i - maxlen。例如,结束位置是3,长度是3,起始位置是3-3+1=1?或者我的计算可能有问题。
假设结束索引是k,长度为L,那么起始索引是k - L +1。例如,k=3,L=3,则起始索引是3-3+1=1。这在0-based的索引中是正确的。例如,索引1、2、3组成的子串长度为3。所以,当结束位置是i-1,那么起始位置是 (i-1) - maxlen +1 = i - maxlen。或者,可能我弄混了。例如,结束位置是i-1,长度为maxlen,则起始位置是 (i-1) - (maxlen-1) = i - maxlen。例如,i-1是3,maxlen是3,那么起始位置是3 -2 =1。是的,所以起始位置是i- maxlen。例如,i是4(对应原字符串中的3),maxlen是3,i - maxlen=1,正确。所以,pos1 = i - maxlen,这里的i是动态规划中的i,即对应原字符串中的i-1的位置。或者,假设在动态规划中i循环到某个值,那么对应的原字符串的位置是i-1。例如,当i=4,原字符串的位置是3,此时maxlen是3,则起始位置是4-3=1,对应原字符串的位置是1。是的。所以,最终的pos1是 i - maxlen,pos2是 j - maxlen。
因此,在每次更新maxlen时,要记录当前的i和j的值。例如,当发现dp[i][j] > maxlen时,记录当前的i和j。这样,当循环结束后,用i和j来计算pos1和pos2。
所以,伪代码大致如下:
maxlen = 0
pos1 = 0
pos2 = 0
for i from 1 to m:
for j from 1 to n:
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] +1
if dp[i][j] > maxlen:
maxlen = dp[i][j]
pos1 = i - maxlen
pos2 = j - maxlen
else:
dp[i][j] =0
这里需要注意,pos1和pos2的起始位置是原字符串中的0-based索引吗?是的,因为s和t的存储结构是定长顺序的,假设它们的起始位置从0开始。这样,返回的pos1和pos2是正确的。
那现在,关于空间优化的问题。如果m和n很大,二维数组可能占用较多空间。例如,当两个字符串的长度都是1000,那么二维数组需要1e6的空间。这在某些情况下可能有问题,但用户的问题中并没有说明是否有空间限制,所以可能可以接受。
如果需要优化空间,可以使用一维数组,因为每次计算dp[i][j]只需要上一行的dp[i-1][j-1]。例如,可以用一个一维数组prev,每次从后往前更新。或者,每次只保留前一行的数据。
例如,可以用一个一维数组dp_prev,然后在每一行处理时,从右到左更新。例如:
初始化一个一维数组dp_prev,长度为n+1,初始化为0。
然后,对于每个i从1到m:
创建一个新的数组dp_current,初始化为0.
for j from1 to n:
if s[i-1] == t[j-1]:
dp_current[j] = dp_prev[j-1] +1
if dp_current[j] > maxlen:
maxlen = dp_current[j]
pos1 = i - maxlen
pos2 = j - maxlen
else:
dp_current[j] =0
dp_prev = dp_current.copy()
这样空间复杂度降为O(n)。或者,更高效的是,可以只用一个一维数组,并在每个j循环中从后往前处理,这样就不会覆盖需要的数据。
例如,使用一个一维数组dp,初始化为0。对于每个i从1到m:
for j from n downto 1:
if s[i-1] == t[j-1]:
dp[j] = dp[j-1] +1
if dp[j] > maxlen:
maxlen = dp[j]
pos1 = i - maxlen
pos2 = j - maxlen
else:
dp[j] =0
这样,每次处理j时,dp[j-1]还是上一轮的结果,不会被覆盖。这样空间复杂度是O(n)。这可能更高效。这种方法是否正确?
比如,当i=1时,处理j从n到1:
如果s[0] == t[j-1],则dp[j] = dp[j-1] +1。但dp[j-1]初始是0,所以如果j=1,则dp[1] = dp[0] +1 =0+1=1(如果s[0]等于t[0])。这样是对的。
这样,每次处理i时,j从后到前,确保在计算dp[j]时,dp[j-1]是上一轮i-1时的值。这样,就能正确计算当前行的dp值。是的,这应该可行。
所以,空间优化后的算法可以用一维数组,时间复杂度O(mn),空间O(n)。这在处理大字符串时更节省内存。
但用户可能只需要正确的算法,所以这里可以选择是否进行空间优化。根据题目的要求,是否需要考虑空间问题?题目没有特别指出,所以可能只需要正确的实现即可。
综上,算法的大体步骤是:
1. 初始化变量maxlen、pos1、pos2为0。
2. 使用动态规划的方法,比较s和t的每个字符,记录以当前字符结尾的最长公共子串长度。
3. 在比较过程中,更新maxlen和对应的起始位置。
4. 最后,通过记录的i和j计算起始位置。
接下来,需要考虑代码的具体实现。假设SString的结构体定义为:
#define MAXLEN 255
typedef struct {
char ch[MAXLEN +1]; // 0号位置存放字符,或者从1开始?
int length;
} SString;
这里需要注意的是,定长顺序存储结构可能有不同的实现方式。例如,有的教材中,0号位置可能不存储字符,而是从1号位置开始存储,并且length表示实际长度。或者,0号位置存储第一个字符,length是实际长度。这里需要明确用户给出的SString的具体定义。
假设SString的ch数组从1开始存储,ch[0]不使用,而length为实际长度。那么,当访问字符时,s的第i个字符是s.ch[i],其中i从1到s.length。这可能更常见,因为这样在代码中处理的时候,索引可以更直观。例如,在动态规划的二维数组中,i和j可以对应到s的第i个字符和t的第j个字符。
但根据用户的函数参数,函数参数中的pos1和pos2是起始位置,应该与SString的存储方式一致。例如,如果SString的字符存储从1开始,那么pos1和pos2的起始位置也是从1开始。或者,如果存储从0开始,那么起始位置也是从0开始。这需要明确。
假设用户的SString定义中,字符存储从索引1开始,那么对应的起始位置应该从1开始。因此,在计算pos1和pos2时,需要根据具体情况调整。
例如,在动态规划的循环中,i和j从1到s.length和t.length。当s.ch[i] == t.ch[j],则dp[i][j] = dp[i-1][j-1] +1。此时,当maxlen被更新为dp[i][j],对应的i和j是当前的位置,那么起始位置是i - maxlen +1,j - maxlen +1吗?
比如,假设字符存储从1开始,i=3的位置对应第三个字符。如果maxlen是2,那么起始位置是3-2 +1 =2。这样,子串从2到3,长度2。是的。
或者在之前的例子中,结束位置是i,maxlen是长度,那么起始位置是i - maxlen +1。比如,i=3,maxlen=2,则起始位置是3-2+1=2,正确。或者,如果i是结束位置的索引,则起始位置是i - maxlen +1。这可能更准确。
此时,我之前在动态规划中的i和j对应的是字符串的索引,即从1开始。那么,假设i是当前字符的位置,那么起始位置是i - maxlen +1吗?
例如,假设当前最长子串的结束位置是i和j,长度为maxlen,那么起始位置是i - maxlen +1和j - maxlen +1。例如,结束位置是i=3,长度maxlen=3,那么起始位置是3-3+1=1。是的,这样是正确的。
所以,当字符存储从1开始的话,在动态规划中的i和j对应的是字符的位置,因此起始位置是i - maxlen +1吗?或者之前的例子中的计算是否正确?
假设字符存储从索引1开始,那么:
当i是当前字符的位置,对应的结束位置是i,maxlen是长度,则起始位置是i - maxlen +1。例如,i=3,maxlen=3,那么起始位置是3-3+1=1,即从1到3的位置,共3个字符。是的,这样正确。
所以,在代码中,当maxlen被更新时,记录当前的i和j,那么pos1 = i - maxlen +1,pos2 = j - maxlen +1。但是,这需要根据SString的具体存储方式而定。
但是,题目中的函数参数是pos1和pos2,作为起始位置。如果SString的存储是从0开始的,那么应该返回0-based的索引。所以,用户的问题中,需要明确SString的存储结构。但题目中没有给出具体定义,这可能需要假设。或者,可能用户认为,起始位置是从0开始计算的。
假设字符存储从0开始,那么当i是动态规划中的行号,对应到s的第i-1个字符。例如,在动态规划中i从1到m,对应s的字符索引0到m-1。此时,当maxlen被更新时,i是动态规划的行号,对应到s的字符索引i-1。此时,起始位置的计算是:
起始位置在s中的索引是 (i-1) - (maxlen-1) = i - maxlen.
例如,i=4(对应字符索引3),maxlen=3,起始位置是4-3=1(索引1),即字符1到3,共3个字符。是的。
同样的,对于t来说,j对应的是字符索引j-1,起始位置是j - maxlen.
因此,在代码中,当记录i和j的值时,pos1 = i - maxlen,pos2 = j - maxlen。
例如,当i=4,j=5,maxlen=3,那么pos1=4-3=1,pos2=5-3=2。对应s中从索引1开始,t中从索引2开始,长度3的子串。
综上,正确的起始位置应为pos1 = i - maxlen,pos2 = j - maxlen,其中i和j是动态规划数组中的索引,对应字符的索引是i-1和j-1。
现在,编写具体的代码:
首先,函数void maxcommonstr(SString s, SString t, int &maxlen, int &pos1, int &pos2)
假设SString的结构体中,ch数组从0开始存储,或者从1开始?
这会影响代码的索引计算。例如,如果ch[0]是第一个字符,那么i从1到s.length循环时,s.ch[i-1]是第i个字符。
或者,如果ch[1]是第一个字符,那么s.ch[i]是第i个字符。
这个问题必须明确。例如,假设SString的ch数组从1开始存储,那么代码中s的第i个字符是s.ch[i]。此时,动态规划中的i对应的是s的第i个字符,所以i的范围是1到s.length。而对应的字符比较是s.ch[i] == t.ch[j]。
但题目中的函数参数是SString类型,而具体实现中如何处理索引,这需要看SString的具体定义。假设用户使用严蔚敏的《数据结构》中的定长顺序存储结构,即:
#define MAXSTRLEN 255
typedef unsigned char SString[MAXSTRLEN +1]; // 0号单元存放串的长度
此时,SString的0号单元存放长度,字符从1号单元开始存放。例如,SString s的s[0]是长度,s[1]到s[s[0]]是字符。
在这种情况下,动态规划中的i和j对应的是字符的位置,从1到s[0]和t[0]。
那么,在比较字符时,是s.ch[i]和t.ch[j]。例如,i的范围是1到s.length,j是1到t.length。
此时,在动态规划数组中的i和j对应的是字符的位置,所以起始位置的计算应为:
pos1 = i - maxlen +1
pos2 = j - maxlen +1
例如,当maxlen是3,i是3,那么起始位置是3-3+1=1,即从位置1到3,共3个字符。
这样,在代码中:
初始化maxlen为0,pos1和pos2为0。
创建二维数组dp[s.length +1][t.length +1],初始化为0.
然后,循环i从1到s.length,j从1到t.length:
if s.ch[i] == t.ch[j]:
dp[i][j] = dp[i-1][j-1] +1
if dp[i][j] > maxlen:
maxlen = dp[i][j]
pos1 = i - maxlen +1
pos2 = j - maxlen +1
else:
dp[i][j] =0
这样,pos1和pos2就是字符在s和t中的起始位置(从1开始)。
例如,当i=3,maxlen=3,那么pos1=3-3+1=1,即起始位置是1,对应字符1到3。
这样,返回的pos1和pos2是正确的。
综上,代码的正确性取决于SString的具体定义。这里需要假设SString的字符存储从索引1开始,0号单元存放长度。这种情况下,动态规划中的i和j对应字符的位置,起始位置的计算如上述。
因此,正确的算法步骤是:
- 使用动态规划表格,记录以每个i和j结尾的最长公共子串长度。
- 每次发现更长的子串时,更新maxlen并记录i和j的位置。
- 最后,根据记录的i和j的位置,计算起始位置。
另外,需要考虑边界情况,比如其中一个字符串为空,或者没有公共子串的情况。此时,maxlen为0,pos1和pos2可能为0或无效值,但根据函数参数的定义,应该由调用者处理这些情况。
现在,可以编写具体的代码:
void maxcommonstr(SString s, SString t, int &maxlen, int &pos1, int &pos2) {
int m = s[0]; // s的长度存放在s[0]
int n = t[0]; // t的长度存放在t[0]
maxlen = 0;
pos1 = 0;
pos2 = 0;
// 创建DP表,大小 (m+1) x (n+1)
int dp[m+1][n+1];
// 初始化DP表为0
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
dp[i][j] = 0;
}
}
// 动态规划填表
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s[i] == t[j]) {
dp[i][j] = dp[i-1][j-1] +1;
if (dp[i][j] > maxlen) {
maxlen = dp[i][j];
pos1 = i - maxlen +1; // 因为i是结束位置,起始位置是i - maxlen +1
pos2 = j - maxlen +1;
}
} else {
dp[i][j] = 0;
}
}
}
}
这里需要注意,如果maxlen仍然为0,则pos1和pos2可能未被更新,保持初始值0。这可能需要根据具体需求处理,例如,当没有公共子串时,返回maxlen=0,pos1和pos2为0或者-1之类的无效值。但根据题目要求,函数参数是带出参数,所以初始化为0即可。
但是,上述代码的空间复杂度是O(mn),当m和n较大时可能存在问题。为了优化空间,可以使用一维数组:
void maxcommonstr(SString s, SString t, int &maxlen, int &pos1, int &pos2) {
int m = s[0];
int n = t[0];
maxlen = 0;
pos1 = 0;
pos2 = 0;
int prev, temp;
// 使用一维数组,长度为n+1
int dp[n+1];
for (int j =0; j <=n; j++) {
dp[j] =0;
}
for (int i =1; i <=m; i++) {
prev = dp[0]; // 保存左上角的值
for (int j =1; j <=n; j++) {
temp = dp[j]; // 保存当前dp[j]的值,作为下一轮的prev
if (s[i] == t[j]) {
dp[j] = prev +1;
if (dp[j] > maxlen) {
maxlen = dp[j];
pos1 = i - maxlen +1;
pos2 = j - maxlen +1;
}
} else {
dp[j] =0;
}
prev = temp;
}
}
}
这个优化后的版本使用一维数组,每次保存前一行的dp值。prev变量保存dp[i-1][j-1]的值。每次处理j的时候,prev被更新为前一行的j-1的值,然后在处理j时,计算出dp[j],并保存temp作为下一列的prev。
这样可以节省空间,但可能比较难理解。不过,这样的空间优化是有效的。
综上,用户需要的最长公共子串的算法可以通过动态规划实现,时间复杂度O(mn),空间复杂度O(n)。在代码中需要注意SString的存储方式,假设字符从索引1开始,长度存储在0号位置,那么起始位置的计算是正确的。
最后,测试一些例子:
例如,s = "abcde",存储为[5, 'a','b','c','d','e'],t = "aabc",存储为[4, 'a','a','b','c']。最长公共子串是 "abc" 吗?或者 "bc"?
不,最长公共子串可能看具体的情况。比如,s是 "abcde",t是 "aabc"。公共子串有 "a"(s的1,t的1),"ab"(s的1-2,t的2-3),"bc"(s的2-3,t的3-4),或者 "abc"在s中是1-3,在t中是 2-4?
假设s的字符是 a b c d e,t的是 a a b c。那么最长公共子串是 "abc"吗?在s中位置1-3,在t中位置2-4?
但是,在t中,位置2-4的字符是 a b c,而 s中的位置1-3是 a b c。因此,公共子串是 "abc",长度3。因此,maxlen=3,pos1=1,pos2=2.
按照动态规划算法,当i=3(s的字符c),j=4(t的字符c),此时s[3]=c,t[4]=c。dp[3][4] = dp[2][3]+1。假设dp[2][3]是2(因为 s的字符b和t的字符b在i=2,j=3时,dp[2][3]=2),那么 dp[3][4]=3。这样,maxlen会被更新为3,pos1=3-3+1=1,pos2=4-3+1=2。正确。
这说明算法是正确的。
另一个例子,s和t没有公共子串,算法返回maxlen=0,pos1和pos2=0。
综上,这个算法可以正确处理各种情况。</think>下面介绍如何设计该算法:
### 算法思路
使用动态规划法求解最长公共子串问题。核心思想是构建一个二维数组 `dp[i][j]`,表示以 `s[i]` 和 `t[j]` 结尾的最长公共子串的长度。通过遍历两个字符串的所有字符组合,逐步计算并更新最大长度及对应位置。
### 算法步骤
1. **初始化参数**
- `maxlen` 记录最长长度(初始为0)
- `pos1` 和 `pos2` 记录子串在 `s` 和 `t` 中的起始位置
2. **构建动态规划表**
- 若 `s[i] == t[j]`,则 `dp[i][j] = dp[i-1][j-1] + 1`
- 否则 `dp[i][j] = 0`
- 每次更新时检查并更新 `maxlen` 及位置
3. **计算起始位置**
- 当发现更长的子串时,通过当前索引 `i` 和 `j` 反推起始位置:
```
pos1 = i - maxlen + 1 // s 中起始位置
pos2 = j - maxlen + 1 // t 中起始位置
```
### 代码实现(空间优化版)
```cpp
void maxcommonstr(SString s, SString t, int &maxlen, int &pos1, int &pos2) {
maxlen = pos1 = pos2 = 0;
int m = s[0], n = t[0]; // 假设 SString 的 0 号单元存储长度
int dp[n+1] = {0}; // 一维数组优化空间
for (int i = 1; i <= m; i++) {
int prev = 0; // 保存左上角的值(dp[i-1][j-1])
for (int j = 1; j <= n; j++) {
int temp = dp[j]; // 记录当前值供下一轮使用
if (s[i] == t[j]) {
dp[j] = prev + 1;
if (dp[j] > maxlen) { // 发现更长公共子串
maxlen = dp[j];
pos1 = i - maxlen + 1; // 计算起始位置
pos2 = j - maxlen + 1;
}
} else {
dp[j] = 0;
}
prev = temp; // 更新左上角的值
}
}
}
```
### 关键点说明
- **时间复杂度**:$O(m \times n)$,双重循环遍历两个字符串
- **空间复杂度**:$O(n)$,使用一维数组优化空间
- **起始位置计算**:通过 `i - maxlen + 1` 和 `j - maxlen + 1` 推导子串起始索引
- **边界处理**:当无公共子串时,`maxlen` 保持为 0,位置参数无意义
### 示例分析
假设 `s = "abcabc"`,`t = "ababc"`:
- 最长公共子串为 `"abc"`(长度 3)
- `s` 中起始位置为 1(对应索引 1),`t` 中为 2(对应索引 2)
该算法能有效处理各种情况,包括无公共子串、多个相同长度子串等场景。
阅读全文
相关推荐

















