C 618 564 Bytes
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;}
Und hier wird es für "Lesbarkeit" enträtselt:
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;
}
Meine Damen und Herren, ich habe einen schrecklichen Fehler gemacht. Es verwendet bisschen besser zu sein ... Und gehe weniger ... Spätestens jetzt ist es schnell .
Wir definieren eine rekursive Funktion L
, die ein Array s
von Zeichenarrays und die Anzahl n
der Zeichenfolgen als Eingabe verwendet. Die Funktion gibt die resultierende Zeichenfolge an stdout aus und gibt im Übrigen die Größe in Zeichen dieser Zeichenfolge zurück.
Die Vorgehensweise
Obwohl der Code kompliziert ist, ist die Strategie hier nicht allzu komplex. Wir beginnen mit einem eher naiven rekursiven Algorithmus, den ich mit Pseudocode beschreiben werde:
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
Nun, dieser Algorithmus alleine ist ziemlich grausam (kann aber in ungefähr 230 Bytes passen, wie ich gefunden habe). So erhält man keine schnellen Ergebnisse. Dieser Algorithmus skaliert unglaublich schlecht mit der Stringlänge. Dieser Algorithmus ist jedoch skaliert ziemlich gut mit einer größeren Anzahl von Strings. Der letzte Testfall würde praktisch sofort gelöst, da keine Zeichenfolgen s
Zeichen c
gemeinsam haben. Es gab zwei Haupttricks, die ich oben implementiert habe und die zu einer unglaublichen Geschwindigkeitssteigerung führten:
L
Überprüfen Sie bei jedem Anruf bei , ob wir zuvor dieselbe Eingabe erhalten haben. Da in der Praxis wird über Zeiger auf den gleichen Satz von Saiten Informationen herumgereicht, haben wir nicht wirklich haben Strings zu vergleichen, nur Orte, die groß ist. Wenn wir feststellen, dass wir diese Informationen bereits erhalten haben, müssen wir die Berechnungen nicht durchgehen (meistens, aber das Abrufen der Ausgabe macht dies etwas komplizierter), und wir können davonkommen, indem wir nur die Länge zurückgeben. Wenn wir uns nicht ein Spiel finden, speichert diesen Satz von Eingabe / Ausgabe für künftige Anrufe zu vergleichen. Im C-Code versucht die zweite for
Schleife, Übereinstimmungen mit der Eingabe zu finden. Bekannte Eingabezeiger werden in gespeichert R
und die entsprechenden Längen- und Zeichenausgabewerte werden in gespeichertA
. Dieser Plan hatte drastische Auswirkungen auf die Laufzeit, insbesondere bei längeren Zeichenfolgen.
Jedes Mal, wenn wir die Standorte von c
in s
finden, besteht die Möglichkeit, dass wir sofort wissen, dass das, was wir gefunden haben, nicht optimal ist. Wenn jede Position c
erscheint nach einigem bekannten Standort eines anderen Briefes, wir wissen automatisch , dass dies c
nicht zu einer optimalen Teilkette führt, weil Sie einen weiteren Buchstaben in ihm passen. Dies bedeutet, dass wir für geringe Kosten möglicherweise mehrere hundert Anrufe L
für große Zeichenfolgen entfernen können . Im obigen C-Code y
ist ein Flag gesetzt, wenn wir automatisch wissen, dass dieses Zeichen zu einer suboptimalen Zeichenfolge führt, und z
ist ein Flag gesetzt, wenn wir ein Zeichen finden, das ausschließlich früher als jedes andere bekannte Zeichen erscheint. Die aktuell frühesten Erscheinungen von Zeichen werden in gespeichertx
. Die derzeitige Umsetzung dieser Idee ist etwas chaotisch, hat aber in vielen Fällen die Leistung nahezu verdoppelt.
Mit diesen beiden Ideen dauerte das, was in einer Stunde nicht fertig war, ungefähr 0,015 Sekunden.
Es gibt wahrscheinlich noch viel mehr kleine Tricks, die die Leistung beschleunigen können, aber zu diesem Zeitpunkt begann ich mir Sorgen um meine Fähigkeit zu machen, alles zu spielen. Ich bin immer noch nicht zufrieden mit dem Golf, also werde ich wahrscheinlich später darauf zurückkommen!
Timings
Hier ist ein Testcode, den ich einlade, online zu testen :
#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;
}
Ich habe die Testfälle des OP auf einem Laptop ausgeführt, der mit einem 1,7-GHz-Intel Core i7-Chip mit einer Optimierungseinstellung von ausgestattet ist -Ofast
. Die Simulation ergab einen Spitzenwert von 712 KB. Hier ist ein Beispiellauf für jeden Testfall mit Zeitangaben:
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
Beim Golfen habe ich die Leistung ziemlich stark beeinträchtigt, und da die Leute die rohe Geschwindigkeit (0,013624 s, um alle Testfälle zusammen abzuschließen) meiner vorherigen 618-Byte-Lösung zu mögen schienen, lasse ich sie hier als Referenz:
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;}
Der Algorithmus selbst bleibt unverändert, aber der neue Code basiert auf Unterteilungen und einigen schwierigeren bitweisen Operationen, die das Ganze verlangsamen.