C,618个 564字节
d,M,N,A[9999][2];char*(R[9999][20]),b[1000];L(char**s,n){char*j[20],c,a=0;int x[n],y=n-1,z,i,t,m=0,w=1;for(;y;)x[y--]=999;for(;y<N;y++){for(i=0;i<n&&s[i]==R[y][i];i++);if(i/n){a=A[y][0];m=A[y][1];w=0;if(m+d<M||!a)goto J;else{c=a;goto K;}}}for(c=97;w&&c<'{';c++){K:t=1,y=1,z=1;for(i=0;i<n;j[i++]++){for(j[i]=s[i];*j[i]-c;j[i]++)t&=!!*j[i];y&=j[i]-s[i]>x[i]?z=0,1:0;}t&=!y;I:if(t){if(z)for(i=0;i<n;i++)x[i]=j[i]-s[i];d++,t+=L(j,n),d--,m=t>m?a=c,t:m;}}if(w){for(y=0;y<n;y++)R[N][y]=s[y];A[N][0]=a;A[N++][1]=m;}J:if(d+m>=M)M=d+m,b[d]=a;if(!d)N=0,M=0,puts(b);return m;}
此处针对“可读性”进行了阐述:
d,M,N,A[9999][2];
char*(R[9999][20]),b[1000];
L(char**s,n){
char*j[20],c,a=0;
int x[n],y=n-1,z,i,t,m=0,w=1;
for(;y;)
x[y--]=999;
for(;y<N;y++){
for(i=0;i<n&&s[i]==R[y][i];i++);
if(i/n){
a=A[y][0];
m=A[y][1];
w=0;
if(m+d<M||!a)
goto J;
else{
c=a;
goto K;
}
}
}
for(c=97;w&&c<'{';c++){
K:
t=1,
y=1,
z=1;
for(i=0;i<n;j[i++]++){
for(j[i]=s[i];*j[i]-c;j[i]++)
t&=!!*j[i];
y&=j[i]-s[i]>x[i]?z=0,1:0;
}
t&=!y;
I:
if(t){
if(z)
for(i=0;i<n;i++)
x[i]=j[i]-s[i];
d++,
t+=L(j,n),
d--,
m=t>m?a=c,t:m;
}
}
if(w){
for(y=0;y<n;y++)R[N][y]=s[y];
A[N][0]=a;
A[N++][1]=m;
}
J:
if(d+m>=M)
M=d+m,b[d]=a;
if(!d)
N=0,M=0,puts(b);
return m;
}
女士们,先生们,我犯了一个可怕的错误。它使用是漂亮......和GOTO少......至少现在它是快。
我们定义了一个递归函数L
,该函数将s
字符数组和n
字符串数作为输入。函数将结果字符串输出到stdout,并附带返回该字符串的大小(以字符为单位)。
该方法
尽管代码很复杂,但是这里的策略并不是太复杂。我们从一个相当幼稚的递归算法开始,我将用伪代码来描述它:
Function L (array of strings s, number of strings n), returns length:
Create array of strings j of size n;
For each character c in "a-z",
For each integer i less than n,
Set the i'th string of j to the i'th string of s, starting at the first appearance of c in s[i]. (e.g. j[i][0] == c)
If c does not occur in the i'th string of s, continue on to the next c.
end For
new_length := L( j, n ) + 1; // (C) t = new_length
if new_length > best_length
best_character := c; // (C) a = best_character
best_length := new_length; // (C) m = best_length
end if
end For
// (C) d = current_depth_in_recursion_tree
if best_length + current_depth_in_recursion_tree >= best_found
prepend best_character to output_string // (C) b = output_string
// (C) M = best_found, which represents the longest common substring found at any given point in the execution.
best_found = best_length + current_depth;
end if
if current_depth_in_recursion_tree == 0
reset all variables, print output_string
end if
return best_length
现在,该算法本身非常糟糕(但我发现它可以容纳约230个字节)。这不是获得快速结果的方式。该算法在字符串长度方面的缩放比例非常差。但是,此算法在使用大量字符串时确实可以很好地扩展。最后一个测试用例几乎可以立即解决,因为其中没有字符串s
有任何c
共同的字符。我在上面实现了两个主要技巧,从而使速度得到了惊人的提高:
在对的每次调用时L
,请检查之前是否都得到了相同的输入。由于实际上信息是通过指针传递给同一组字符串的,因此实际上我们不必比较字符串,而只是比较位置,这很棒。如果我们发现之前已经获得了这些信息,则无需遍历所有计算(大多数情况下,但是获取输出会使此过程变得更加复杂),并且我们只需返回长度即可。如果我们没有找到匹配,保存这组输入/输出进行比较,以未来的呼叫。在C代码中,第二个for
循环尝试查找与输入匹配的内容。已知的输入指针保存在中R
,相应的长度和字符输出值存储在中A
。该计划对运行时产生了巨大影响,尤其是对于较长的字符串。
每当我们找到c
in 的位置时s
,就有机会立即知道我们发现的不是最佳的。如果的每个位置都c
出现在另一个字母的某个已知位置之后,我们会自动知道这c
不会导致最佳子字符串,因为您可以在其中再加上一个字母。这意味着只需很小的成本,我们就可以删除L
大型字符串的数百个调用。在上面的C代码中,y
如果我们自动知道该字符导致次优字符串,z
则设置一个标志;如果我们发现一个字符的出现比任何其他已知字符都早的字符,则设置一个标志。当前最早出现的字符存储在x
。该想法的当前实现有些混乱,但在许多情况下性能几乎提高了一倍。
有了这两个主意,一个小时之内无法完成的工作大约花费了0.015秒。
可能还有更多的小技巧可以提高性能,但是这时我开始担心自己打高尔夫球的能力。我仍然对高尔夫不满意,所以我稍后可能会再谈!
时机
这是一些测试代码,我邀请您在线尝试:
#include "stdio.h"
#include "time.h"
#define SIZE_ARRAY(x) (sizeof(x) / sizeof(*x))
int main(int argc, char** argv) {
/* Our test case */
char* test7[] = {
"nqrualgoedlf",
"jgqorzglfnpa",
"fgttvnogldfx",
"pgostsulyfug",
"sgnhoyjlnfvr",
"wdttgkolfkbt"
};
printf("Test 7:\n\t");
clock_t start = clock();
/* The call to L */
int size = L(test7, SIZE_ARRAY(test7));
double dt = ((double)(clock() - start)) / CLOCKS_PER_SEC;
printf("\tSize: %d\n", size);
printf("\tElapsed time: %lf s\n", dt);
return 0;
}
我在配备1.7 GHz Intel Core i7芯片且优化设置为的笔记本电脑上运行了OP的测试用例-Ofast
。仿真报告需要712KB的峰值。这是每个测试用例的运行示例,并带有计时:
Test 1:
a
Size: 1
Elapsed time: 0.000020 s
Test 2:
x
Size: 1
Elapsed time: 0.000017 s
Test 3:
hecbpyhogntqppcqgkxchpsieuhbmcbhuqdjbrqmclchqyfhtdvdoysuhrrl
Size: 60
Elapsed time: 0.054547 s
Test 4:
ihicvaoodsnktkrar
Size: 17
Elapsed time: 0.007459 s
Test 5:
krkk
Size: 4
Elapsed time: 0.000051 s
Test 6:
code
Size: 4
Elapsed time: 0.000045 s
Test 7:
golf
Size: 4
Elapsed time: 0.000040 s
Test 8:
Size: 0
Elapsed time: 0.000029 s
Total time: 0.062293 s
在高尔夫运动中,我的表现相当出色,并且由于人们似乎喜欢我以前的618字节解决方案的蛮力速度(完成所有测试用例需要0.013624 s),因此在这里我将其留作参考:
d,M,N,A[9999][2];char*(R[9999][20]),b[1000];L(char**s,n){char*j[20],c,a=0;int x[n],y,z,i,t,m=0,w=1;for(y=0;y<n;y++)x[y]=999;for(y=0;y<N;y++){for(i=0;i<n;i++)if(s[i]!=R[y][i])break;if(i==n){a=A[y][0];m=A[y][1];w=0;if(m+d<M||!a)goto J;else{c=a;goto K;}}}for(c=97;w&&c<'{';c++){K:t=1,y=1,z=1;for(i=0;i<n;j[i++]++){for(j[i]=s[i];*j[i]-c;j[i]++)if(!*j[i]){t=0;goto I;}if(j[i]-s[i]>x[i])z=0;if(j[i]-s[i]<x[i])y=0;}if(y){t=0;}I:if(t){if(z){for(i=0;i<n;i++){x[i]=j[i]-s[i];}}d++,t+=L(j,n),d--,m=t>m?(a=c),t:m;}}if(w){for(y=0;y<n;y++)R[N][y]=s[y];A[N][0]=a;A[N++][1]=m;}J:if(d+m>=M)M=d+m,b[d]=a;if(!d)N=0,M=0,puts(b);return m;}
该算法本身并没有改变,但是新代码依赖于除法和一些棘手的按位运算,最终使整个过程变慢。