在“ n”个二进制字符串中找到最长的公共子字符串的长度

时间:2018-10-02 22:04:40

标签: c++ string algorithm optimization longest-substring

我给了n个字符串(n> = 2和n <= 4),每个字符串仅使用2个字母构成:ab。在这组字符串中,我必须找到所有字符串中存在的最长的公共子字符串的长度。保证存在解决方案。我们来看一个例子:

n=4
abbabaaaaabb
aaaababab
bbbbaaaab
aaaaaaabaaab

The result is 5 (because the longest common substring is "aaaab").

我不必打印(甚至不知道)子字符串,只需要打印其长度即可。

还假定即使每个字符串的长度可以高达60,结果也不能大于13 000

我要尝试的是:找到给定字符串中任何字符串的最小长度,然后将其与60进行比较,并在两者之间选择最小值作为starting point。然后,我开始获取第一个字符串的序列,第一个字符串的每个序列的长度为len,其中len的取值范围为starting point1。在每次迭代中,我都会采用长度为len的第一个字符串的所有可能序列,并将其用作pattern。使用KMP算法(因此,O(n+m)的复杂性),我遍历了所有其他字符串(从2n),并检查是否在字符串{中找到了pattern {1}}。每当找不到它时,我都会中断迭代并尝试使用长度为i的下一个序列,或者,如果没有序列,我将减少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 ,因为它看起来像是我没用过的一件大事,但是我对如何准确使用这些信息一无所知。我将不胜感激。

1 个答案:

答案 0 :(得分:2)

答案是分别构建所有字符串的后缀树,然后将它们相交。后缀树就像一个特里树,它同时包含一个字符串的所有后缀。

O(n)Ukkonen's algorithm构建一个固定字母的后缀树。 (如果您不喜欢该说明,则可以使用Google查找其他人。)如果您有m个大小为n的树,那么这就是时间O(nm)

相交的后缀树是并行遍历它们的问题,只有在所有树都可以走得更远时才走得更远。如果您有m个大小为n的树,则可以在不超过O(nm)的时间内完成此操作。

此算法的总时间为时间O(nm)。既然只是读字符串是时候了O(nm),那么您再做不到。


添加少量细节,假设后缀树写为每个节点一个字符。因此,每个节点只是一个字典,其键是字符,其值是树的其余部分。因此,以我们的示例为例,对于字符串ABABAhttps://imgur.com/a/tnVlSI1处的图将变成这样的数据结构,如下所示:

{
    '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 ++中,出于相同的目的,有一组指针元组。)