我给了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").
我不必打印(甚至不知道)子字符串,只需要打印其长度即可。
还假定即使每个字符串的长度可以高达60
,结果也不能大于13 000
。
我要尝试的是:找到给定字符串中任何字符串的最小长度,然后将其与60
进行比较,并在两者之间选择最小值作为starting point
。然后,我开始获取第一个字符串的序列,第一个字符串的每个序列的长度为len
,其中len
的取值范围为starting point
到1
。在每次迭代中,我都会采用长度为len
的第一个字符串的所有可能序列,并将其用作pattern
。使用KMP算法(因此,O(n+m)
的复杂性),我遍历了所有其他字符串(从2
到n
),并检查是否在字符串{中找到了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
,因为它看起来像是我没用过的一件大事,但是我对如何准确使用这些信息一无所知。我将不胜感激。
答案 0 :(得分:2)
答案是分别构建所有字符串的后缀树,然后将它们相交。后缀树就像一个特里树,它同时包含一个字符串的所有后缀。
为O(n)
和Ukkonen's algorithm构建一个固定字母的后缀树。 (如果您不喜欢该说明,则可以使用Google查找其他人。)如果您有m
个大小为n
的树,那么这就是时间O(nm)
。
相交的后缀树是并行遍历它们的问题,只有在所有树都可以走得更远时才走得更远。如果您有m
个大小为n
的树,则可以在不超过O(nm)
的时间内完成此操作。
此算法的总时间为时间O(nm)
。既然只是读字符串是时候了O(nm)
,那么您再做不到。
添加少量细节,假设后缀树写为每个节点一个字符。因此,每个节点只是一个字典,其键是字符,其值是树的其余部分。因此,以我们的示例为例,对于字符串ABABA
,https://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 ++中,出于相同的目的,有一组指针元组。)