C, 618 564 byte
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;}
E qui è svelato, per "leggibilità":
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;
}
Onorevoli colleghi, ho fatto un terribile errore. Un tempo era più bello ... E meno goto ... Almeno ora è veloce .
Definiamo una funzione ricorsiva Lche accetta come input una matrice sdi matrici di caratteri e il numeron di stringhe. La funzione restituisce la stringa risultante a stdout e, per inciso, restituisce la dimensione in caratteri di quella stringa.
L'approccio
Sebbene il codice sia contorto, la strategia qui non è troppo complessa. Iniziamo con un algoritmo ricorsivo piuttosto ingenuo, che descriverò con lo pseudocodice:
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
Ora, questo algoritmo da solo è piuttosto atroce (ma può essere inserito in circa ~ 230 byte, ho scoperto). Non è così che si ottengono risultati rapidi. Questo algoritmo si ridimensiona incredibilmente male con la lunghezza della stringa. Questo algoritmo ha , tuttavia, scala abbastanza bene con un numero maggiore di stringhe. L'ultimo caso di test verrebbe risolto praticamente all'istante, poiché nessuna stringa sha caratteri cin comune. Ci sono stati due trucchi principali che ho implementato sopra che hanno portato a un incredibile aumento della velocità:
Ad ogni chiamata a L, controlla se ci è stato dato lo stesso input prima. Dato che in pratica le informazioni vengono passate attraverso i puntatori allo stesso set di stringhe, in realtà non dobbiamo confrontare stringhe, ma solo posizioni, il che è fantastico. Se scopriamo di aver ottenuto queste informazioni prima, non è necessario eseguire i calcoli (la maggior parte delle volte, ma ottenere l'output rende questo un po 'più complicato) e possiamo farcela semplicemente restituendo la lunghezza. Se non troviamo una corrispondenza, salva questo set di input / output per confrontarlo con le chiamate future. Nel codice C, il secondofor ciclo tenta di trovare corrispondenze all'input. I puntatori di input noti vengono salvati e vengono memorizzati Ri corrispondenti valori di lunghezza e output dei caratteriA. Questo piano ha avuto un effetto drastico sul runtime, specialmente con stringhe più lunghe.
Ogni volta che troviamo le posizioni di cin s, c'è una possibilità che sappiamo subito che ciò che abbiamo trovato non è ottimale. Se ogni posizione di cappare dopo una posizione nota di un'altra lettera, sappiamo automaticamente che ciò cnon porta a una sottostringa ottimale, perché puoi inserire un'altra lettera in essa. Ciò significa che per un piccolo costo, possiamo potenzialmente rimuovere diverse centinaia di chiamate Lper stringhe di grandi dimensioni. Nel codice C sopra, yè impostato un flag se sappiamo automaticamente che questo carattere porta a una stringa non ottimale, ed zè un flag impostato se troviamo un carattere che ha apparenze esclusivamente precedenti rispetto a qualsiasi altro carattere noto. Le attuali prime apparizioni di personaggi sono memorizzate inx. L'attuale implementazione di questa idea è un po 'confusa, ma quasi raddoppia le prestazioni in molti casi.
Con queste due idee, ciò che non è finito in un'ora ora ha impiegato circa 0,015 secondi.
Probabilmente ci sono molti altri piccoli trucchi che possono accelerare le prestazioni, ma a questo punto ho iniziato a preoccuparmi della mia capacità di giocare a golf. Non sono ancora contento del golf, quindi probabilmente ci tornerò più avanti!
Tempi
Ecco un po 'di codice di prova, che ti invito a provare online :
#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;
}
Ho eseguito i casi di test dell'OP su un laptop dotato di un chip Intel Core i7 da 1,7 GHz, con un'impostazione di ottimizzazione di -Ofast. La simulazione ha riportato un picco di 712 KB richiesti. Ecco un esempio di esecuzione di ogni caso di test, con i tempi:
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
Nel golf, ho colpito le prestazioni piuttosto significativamente, e poiché alla gente sembrava piacere la velocità bruta (0,013624 s per completare tutti i casi di test combinati) della mia precedente soluzione a 618 byte, la lascerò qui come riferimento:
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;}
L'algoritmo stesso è invariato, ma il nuovo codice si basa su divisioni e alcune operazioni bit per bit più complicate che finiscono per rallentare il tutto.