[英]Find the length of the longest common substring in 'n' binary strings
我得到了n
字符串(n> = 2和n <= 4),每个字符串仅使用2个字母构造: a
和b
。 在这组字符串中,我必须找到所有字符串中存在的最长的公共子字符串的长度。 保证存在解决方案。 让我们来看一个例子:
n=4
abbabaaaaabb
aaaababab
bbbbaaaab
aaaaaaabaaab
The result is 5 (because the longest common substring is "aaaab").
我不必打印(甚至不知道)子字符串,只需要打印其长度即可。
还假定即使每个字符串的长度可以高达13 000
,结果也不能大于60
。
我尝试的是:找到给定字符串中任何字符串的最小长度,然后将其与60
进行比较,然后选择两者之间的最小值作为starting point
。 然后,我开始获取第一个字符串的序列,并且第一个字符串的每个序列的长度为len
,其中len
接受从starting point
到1
值。 在每次迭代中,我都会采用长度为len
的第一个字符串的所有可能序列,并将其用作pattern
。 使用KMP算法(因此,复杂度为O(n+m)
),我遍历了所有其他字符串(从2
到n
),并检查是否在字符串i
找到了pattern
。 每当找不到它时,我都会中断迭代并尝试使用长度为len
的下一个序列,或者,如果没有任何序列,则减小len
并尝试所有具有新长度且减小的值len
序列。 但是,如果匹配,我将停止程序并打印长度len
,因为我们从可能的最长长度开始,并在每一步减小,因此逻辑上我们找到的第一个匹配代表最大可能的长度。 这是代码(但实际上并不重要,因为此方法还不够好;我知道我不应该using namespace std
它,但它并不会真正影响该程序,因此我不会打扰):
#include <iostream>
#include <string>
#define nmax 50001
#define result_max 60
using namespace std;
int n,m,lps[nmax],starting_point,len;
string a[nmax],pattern,str;
void create_lps() {
lps[0]=0;
unsigned int len=0,i=1;
while (i < pattern.length()) {
if (pattern[i] == pattern[len]) {
len++;
lps[i] = len;
i++;
}
else {
if (len != 0) {
len = lps[len-1];
}
else {
lps[i] = 0;
i++;
}
}
}
}
bool kmp_MatchOrNot(int index) {
unsigned int i=0,j=0;
while (i < a[index].length()) {
if (pattern[j] == a[index][i]) {
j++;
i++;
}
if (j == pattern.length()) {
return true;
}
else if (i<a[index].length() && pattern[j]!=a[index][i]){
if (j != 0) {
j = lps[j-1];
}
else {
i++;
}
}
}
return false;
}
int main()
{
int i,left,n;
unsigned int minim = nmax;
bool solution;
cin>>n;
for (i=1;i<=n;i++) {
cin>>a[i];
if (a[i].length() < minim) {
minim = a[i].length();
}
}
if (minim < result_max) starting_point = minim;
else starting_point = result_max;
for (len=starting_point; len>=1; len--) {
for (left=0; (unsigned)left<=a[1].length()-len; left++) {
pattern = a[1].substr(left,len);
solution = true;
for (i=2;i<=n;i++) {
if (pattern.length() > a[i].length()) {
solution = false;
break;
}
else {
create_lps();
if (kmp_MatchOrNot(i) == false) {
solution = false;
break;
}
}
}
if (solution == true) {
cout<<len;
return 0;
}
}
}
return 0;
}
关键是:程序可以正常工作,并且给出正确的结果,但是当我在网站上发送代码时,出现了“超过时间限制”错误,所以我只得到一半的分数。
这使我相信,为了更好地解决时间复杂性问题,我必须利用以下事实:字符串的字母只能是a
或b
,因为这看起来像是我的一件大事没有使用,但对于如何使用这些信息我一无所知。 我将不胜感激任何帮助。
答案是分别构建所有字符串的后缀树,然后将它们相交。 后缀树就像一个trie,它同时包含一个字符串的所有后缀。
使用Ukkonen算法 ,为固定的字母构建后缀树为O(n)
。 (如果您不喜欢该说明,则可以使用Google查找其他人。)如果您有m
个大小为n
树,则时间为O(nm)
。
与后缀树相交是并行遍历它们的问题,只有在所有树都可以走得更远时才走得更远。 如果您有m
个大小为n
树,则可以在不超过O(nm)
时间内完成此操作。
该算法的总时间为时间O(nm)
。 鉴于仅读取字符串的时间为O(nm)
,那么您做得更好。
添加少量细节,假设后缀树写为每个节点一个字符。 因此,每个节点只是一个字典,其键是字符,其值是树的其余部分。 因此,以我们的示例为例,对于字符串ABABA
, https: ABABA
的图将变成一种数据结构(如下所示):
{
'A': {
'B': {
'': None,
'A': {
'B': {
'': None
}
}
}
},
'B': {
'': None
'A': {
'B': {
'': None
}
}
}
}
同样, BABA
会变成:
{
'A': {
'': None
'B': {
'A': {
'': None
}
}
},
'B': {
'A': {
'': None,
'B': {
'A': {
'': None
}
}
}
}
}
使用如下所示的数据结构,朴素的Python可以将它们进行比较:
def tree_intersection_depth (trees):
best_depth = 0
for (char, deeper) in trees[0].items():
if deeper is None:
continue
failed = False
deepers = [deeper]
for tree in trees[1:]:
if char in tree:
deepers.append(tree[char])
else:
failed = True
break
if failed:
continue
depth = 1 + tree_intersection_depth(deepers)
if best_depth < depth:
best_depth = depth
return best_depth
您将其称为tree_intersection_depth([tree1, tree2, tree3, ...])
。
有了以上两棵树,确实给出了3
的答案。
现在,我实际上在写出该数据结构时受骗。 使后缀树高效的原因是您实际上没有像这样的数据结构。 您可以重用所有重复的结构。 因此,用于模拟设置数据结构并调用它的代码如下所示:
b_ = {'B': {'': None}}
ab_ = {'': None, 'A': b_}
bab_ = {'B': ab_}
abab = {'A': bab_, 'B': ab_}
a_ = {'A': {'': None}}
ba_ = {'': None, 'B': a_}
aba_ = {'A': ba_}
baba = {'B': aba_, 'A': ba_}
print(tree_intersection_depth([abab, baba]))
现在我们可以看到,要获得预期的性能,需要执行的步骤有所遗漏。 问题是,当树的大小为O(n)
,在搜索树时,我们可能会访问O(n^2)
子字符串。 在您的情况下,您不必担心,因为保证子字符串的深度永远不会超过60。但是在一般情况下,您将需要添加备注,以便在递归结果比较数据结构时以前已经看到过,您会立即返回旧答案,而不是新答案。 (在Python中,您可以使用id()
方法将对象的地址与您之前看到的地址进行比较。在C ++中,出于相同的目的,有一组指针元组。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.