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;
}
신사 숙녀 여러분, 저는 끔찍한 실수를 저질렀습니다. 예전 에는 더 예 뻤고 ... 고 토레스 ... 적어도 지금은 빠릅니다 .
문자 배열과 문자열 수 의 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
에서 의 위치를 찾을 때마다 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.7GHz 인 Intel Core i7 칩 1.7GHz가 장착 된 랩톱에서 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 초를 결합하여 모든 테스트 사례를 완료하는 것)를 좋아하는 것처럼 보였으므로 참조를 위해 여기에 남겨 두겠습니다.
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;}
알고리즘 자체는 변경되지 않았지만 새 코드는 나누기와 일부 까다로운 비트 연산에 의존하여 전체 속도가 느려집니다.