最长公共子串

最长公共子串

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

输入格式

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

输出格式

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

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

数据范围

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

输入样例:

ab123abccff
abcfacb123

输出样例:

3

 

解题思路

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

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

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

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

  AC代码如下,时间复杂度为O(nlogn)

复制代码
 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结尾的所有合法(即不含有数字)的公共子串中长度的最大值。根据aibj是否相同进行状态转移。如果ai=bj,那么f(i,j)=f(i1,j1)+1;否则如果aibj,那么很明显f(i,j)=0

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

  AC代码如下,时间复杂度为O(n×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/

posted @   onlyblues  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
Web Analytics
点击右上角即可分享
微信分享提示