首页 > 其他分享 >最长公共子串

最长公共子串

时间:2023-04-20 20:37:47浏览次数:39  
标签:子串 int text mid 公共 字符串 长度 最长

最长公共子串

给定两个字符串,求这两个字符串的不包含数字的最长公共子串的长度。

输入格式

共两行,每行一个由小写字母和数字构成的字符串。

输出格式

一个整数,表示给定两个字符串的不包含数字的最长公共子串的长度。

如果不存在满足要求的非空公共子串,则输出 $0$。

数据范围

输入字符串的长度均不超过 $10000$。

输入样例:

ab123abccff
abcfacb123

输出样例:

3

 

解题思路

  首先把子串看成子序列,看了样例半天都不知道怎么来的。接着被题目名字误导往dp的方向想,然后看了眼数据范围,发现dp的计算量是${10}^8$级别大概率会TLE(事实上并没有超时,可以参考下面动态规划部分的题解),想去优化发现不会,就不会做了。最后看了眼标签才知道怎么做的。

  首先容易知道答案具有二段性。假设两个字符串中最大的公共子串长度为$\text{ans}$,那么很明显也必然存在长度不超过$\text{ans}$的公共子串。而不会有长度超过$\text{ans}$的公共子串,否则就与假设矛盾了。因此可以用二分。

  因为是连续的子串,因此当二分出$\text{mid}$,我们就分别对两个字符串$a$和$b$枚举出所有长度为$\text{mid}$的子串,分别构成了两个集合,同时还要保证这些子串中不能包含数字。然后看一下这两个集合中是否有交集,即$a$和$b$是否有相同的长度为$\text{mid}$的子串。如果直接这样做的话这是一个$O(n^3)$的时间复杂度,其中$O(n^2)$用来枚举出两个集合中要比对的子串,再用$O(n)$来判断这两个子串是否相同。优化的方法是,先枚举出$b$中所有长度为$\text{mid}$的子串,存到哈希表中,然后再枚举$a$中所有长度为$\text{mid}$的子串,看看哈希表中是否存在相同的子串,这样就把时间复杂度降到了$O(n^2)$了。这还不够,因此我们需要用到字符串哈希,把判断两个子串是否相同的计算量降到$O(1)$,这样时间复杂度就变成了$O(n)$了。

  如何判断子串中是否含有数字呢?很简单,我们对字符串中的数字个数求前缀和,对于区间范围是$[i,j]$的子串,如果$s_j - s_{i-1} = 0$,则表明子串中不存在数字,否则反之。

  AC代码如下,时间复杂度为$O(n \log{n})$:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef unsigned long long ULL;
 5 
 6 const int N = 1e4 + 10, P = 13331;
 7 
 8 int n, m;
 9 char a[N], b[N];
10 int s1[N], s2[N];
11 ULL h1[N], h2[N], p[N];
12 
13 ULL get(ULL *h, int l, int r) {
14     return h[r] - h[l - 1] * p[r - l + 1];
15 }
16 
17 bool check(int mid) {
18     unordered_set<ULL> st;
19     for (int i = mid; i <= m; i++) {    // 把b中长度为mid且不含数字的子串加到哈希表中
20         if (s2[i] - s2[i - mid] == 0) st.insert(get(h2, i - mid + 1, i));
21     }
22     for (int i = mid; i <= n; i++) {
23         // a中长度为mid且不含数字的子串在哈希表中出现,表示存在长度为mid的公共子串
24         if (s1[i] - s1[i - mid] == 0 && st.count(get(h1, i - mid + 1, i))) return true;
25     }
26     return false;
27 }
28 
29 int main() {
30     scanf("%s %s", a + 1, b + 1);
31     n = strlen(a + 1), m = strlen(b + 1);
32     p[0] = 1;
33     for (int i = 1; i <= n || i <= m; i++) {
34         p[i] = p[i - 1] * P;
35     }
36     for (int i = 1; i <= n; i++) {
37         h1[i] = h1[i - 1] * P + a[i];
38         s1[i] = s1[i - 1];
39         if (isdigit(a[i])) s1[i]++;
40     }
41     for (int i = 1; i <= m; i++) {
42         h2[i] = h2[i - 1] * P + b[i];
43         s2[i] = s2[i - 1];
44         if (isdigit(b[i])) s2[i]++;
45     }
46     int l = 0, r = min(n, m);
47     while (l < r) {
48         int mid = l + r + 1 >> 1;
49         if (check(mid)) l = mid;
50         else r = mid - 1;
51     }
52     printf("%d", l);
53     
54     return 0;
55 }

  再给出个动态规划的解法,其实就是最大连续子串和的二维版。

  定义状态$f(i,j)$表示$a$中前$i$个位置且以$i$结尾,$b$中前$j$个位置且以$j$结尾的所有公共子串中长度的最大值。根据$a_i$与$b_j$是否相同进行状态转移。如果$a_i = b_j$,那么$f(i,j) = f(i-1,j-1)+1$;否则如果$a_i \ne b_j$,那么很明显$f(i,j) = 0$。

  其中需要需要把第一维的大小优化到$2$,否则就超内存限制了。根据状态转移方程知道每次更新都取决于第一维的前一个状态,因此只需要开个大小为$2$的数组滚动就好了。

  AC代码如下,时间复杂度为$O(n \times m)$:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int N = 1e4 + 10;
 5 
 6 char a[N], b[N];
 7 int f[2][N];
 8 
 9 int main() {
10     scanf("%s %s", a + 1, b + 1);
11     int n = strlen(a + 1), m = strlen(b + 1);
12     int ret = 0;
13     for (int i = 1; i <= n; i++) {
14         memset(f[i & 1], 0, sizeof(f[0]));
15         for (int j = 1; j <= m; j++) {
16             if (a[i] == b[j] && !isdigit(a[i])) f[i & 1][j] = f[i - 1 & 1][j - 1] + 1;
17             ret = max(ret, f[i & 1][j]);
18         }
19     }
20     printf("%d", ret);
21     
22     return 0;
23 }

 

参考资料

  AcWing 3508. 最长公共子串(春季每日一题2023):https://www.acwing.com/video/4699/

标签:子串,int,text,mid,公共,字符串,长度,最长
From: https://www.cnblogs.com/onlyblues/p/17338202.html

相关文章

  • C#基础 MethodInfo GetMethod 反射 调用有参公共方法
     .NETFramework:4.7.2       IDE:VisualStudioCommunity2019        OS:Windows10x64    typesetting:Markdown codeusingSystem;usingSystem.Reflection;namespaceConsoleApp{publicclassPerson{publicvoidSayH......
  • C#基础 MethodInfo GetMethod 反射 调用无参公共方法
     .NETFramework:4.7.2       IDE:VisualStudioCommunity2019        OS:Windows10x64    typesetting:Markdown codeusingSystem;usingSystem.Reflection;namespaceConsoleApp{publicclassPerson{publicvoidSayH......
  • LOJ #6564 - 最长公共子序列(bitset 求 LCS)
    怎么全天下就我没见过?被薄纱了/ll还是考虑从朴素的DP入手优化。不难发现对于固定的\(i\),相邻的\(dp_{i,j}\)的差要么是\(0\)要么是\(1\),也就是说从压位的考虑角度可能很有前途。因此我们转而维护\(dp_{i,j}\)的差分数组\(v_{i,j}=dp_{i,j}-dp_{i,j-1}\)。考虑新添加一......
  • #yyds干货盘点# LeetCode程序员面试金典:串联所有单词的子串
    题目:给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串长度相同。 s 中的串联子串是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。例如,如果 words=["ab","cd","ef"],那么 "abcdef", "abefcd","cdabef", "cdefab","efabcd",......
  • admin项目公共方法解析
    前言:项目中公用的一些方法,配置,常量等正文:文件:common/inc.go packagecommonconstTimeTem="2006-01-0215:04:05"constAdminSecret="jO4s4QcGs4B8brP2"//随机秘钥//定义一个统一的返回对象typeReDatastruct{StatusboolMsgstringData......
  • Maven导入阿里云公共仓库出错
    <mirror><id>aliyunmaven</id><mirrorOf>*</mirrorOf><name>阿里云公共仓库</name><url>https://maven.aliyun.com/repository/public</url></mirror>  ......
  • 【题解】P6292 区间本质不同子串个数
    原题链接区间本质不同子串个数题目描述给定一个长度为\(n\)的字符串\(S\),\(m\)次询问由\(S\)的第\(L\)到第\(R\)个字符组成的字符串包含多少个本质不同的子串。定义两个字符串\(a,b\)相同当且仅当\(|a|=|b|\)并且对于\(i\in[1,|a|]\)都有\(a_i=b_i\)。输入......
  • 409. 最长回文串
    问题描述给定一个字符串s,返回由s中字母所构造的最长回文串的长度。问题分析符号设定Nch为ch在回文串中出现的次数回文串中最多有一个字符Nch为奇数算法classSolution:deflongestPalindrome(self,s:str)->int:count_ch={}forchins:......
  • python7 用于高级数据类型操作的公共方法
    1.+,*,in‘+’通过此方法可以连接两个数据‘*’通过此方法可以倍数型的复制数据‘in’通过此方法可以查询数据中是否有我们的目标查询数据,返回一个布尔值strA='123'strB='456'print(strA+strB)print(strA*2)print('1'instrA) 注:字符串,列表,元组,字典都可以使用这三种方法......
  • 团体天梯练习 L2-008 最长对称子串
    L2-008最长对称子串对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定IsPAT&TAPsymmetric?,最长对称子串为sPAT&TAPs,于是你应该输出11。输入格式:输入在一行中给出长度不超过1000的非空字符串。输出格式:在一行中输出最长对称子串的长度。输入样例:IsPAT&TAP......