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
ます。上記で実装した2つの主なトリックがあり、信じられないほど速度が向上しました。
を呼び出すたびにL
、以前にこの同じ入力が与えられたかどうかを確認します。実際には、情報は同じ文字列セットへのポインタを介して渡されるため、実際には文字列を比較する必要はなく、場所だけを比較する必要があります。これは素晴らしいことです。以前にこの情報を取得したことがわかった場合、計算を実行する必要はありません(ほとんどの場合、出力を取得することでこれが少し複雑になります)。長さを返すだけで済みます。一致するものが見つからない場合は、この入力/出力のセットを保存して、将来の呼び出しと比較します。Cコードでは、2番目のfor
ループが入力への一致を見つけようとします。既知の入力ポインターはに保存されR
、対応する長さと文字出力値はに保存されますA
。この計画は、特に文字列が長い場合、実行時間に大きな影響を与えました。
c
in の位置を見つけるたびに、見つけたs
ものが最適ではないことがすぐにわかる可能性があります。のすべての場所が別の文字の既知の場所の後にc
表示される場合、これは最適な部分文字列にならないことを自動的に認識します。これは、小さなコストで、大きな文字列に対する数百の呼び出しを潜在的に削除できることを意味します。上記のCコードでは、この文字が次善の文字列につながることが自動的にわかっている場合はフラグセットであり、他の既知の文字よりも前に出現する文字のみが見つかった場合はフラグセットです。キャラクターの現在の最も早い出現はに保存されますc
L
y
z
x
。このアイデアの現在の実装は少し厄介ですが、多くの場合パフォーマンスがほぼ2倍になります。
これら2つのアイデアにより、1時間で終了しなかったものは約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秒)を好むように思われたので、参照用にここに残します。
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;}
アルゴリズム自体は変更されていませんが、新しいコードは除算といくつかのトリッキーなビット演算に依存しているため、全体が遅くなります。