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;}
Dan di sini terurai, untuk "keterbacaan":
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;
}
Hadirin sekalian, saya telah melakukan kesalahan yang mengerikan. Itu digunakan untuk menjadi cantik ... Dan goto-kurang ... Setidaknya sekarang itu cepat .
Kami mendefinisikan fungsi rekursif L
yang mengambil input array s
array karakter dan jumlah n
string. Fungsi mengeluarkan string yang dihasilkan ke stdout, dan secara tidak sengaja mengembalikan ukuran karakter string itu.
Pendekatan
Meskipun kodenya berbelit-belit, strategi di sini tidak terlalu rumit. Kita mulai dengan algoritma rekursif yang agak naif, yang akan saya uraikan dengan pseudocode:
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
Sekarang, algoritma ini sendiri cukup mengerikan (tapi bisa muat sekitar ~ 230 byte, saya temukan). Ini bukan bagaimana seseorang mendapatkan hasil yang cepat. Algoritma ini berskala sangat buruk dengan panjang string. Algoritma ini , bagaimanapun, skala cukup baik dengan jumlah string yang lebih besar. Kasing terakhir akan dipecahkan secara instan, karena tidak ada string yang s
memiliki karakter yang c
sama. Ada dua trik utama yang saya terapkan di atas yang menghasilkan peningkatan kecepatan luar biasa:
Di setiap panggilan L
, periksa apakah kami telah diberi input yang sama sebelumnya. Karena dalam praktiknya informasi diteruskan melalui pointer ke set string yang sama, kita tidak benar - benar harus membandingkan string, hanya lokasi, yang bagus. Jika kami menemukan bahwa kami telah mendapatkan informasi ini sebelumnya, tidak perlu menjalankan perhitungan (sebagian besar waktu, tetapi mendapatkan output membuat ini sedikit lebih rumit) dan kami bisa lolos hanya dengan mengembalikan panjangnya. Jika kami tidak menemukan kecocokan, simpan rangkaian input / output ini untuk dibandingkan dengan panggilan berikutnya. Dalam kode C, for
loop kedua mencoba menemukan kecocokan dengan input. Pointer input yang diketahui disimpan R
, dan nilai output karakter dan panjang yang sesuai disimpanA
. Paket ini memiliki efek drastis pada runtime, terutama dengan string yang lebih panjang.
Setiap kali kita menemukan lokasi dari c
dalam s
, ada kesempatan kita tahu langsung dari kelelawar bahwa apa yang kita temukan adalah tidak optimal. Jika setiap lokasi c
muncul setelah beberapa lokasi diketahui dari surat lain, kami secara otomatis tahu bahwa ini c
tidak mengarah ke substring yang optimal, karena Anda dapat memasukkan satu huruf lagi di dalamnya. Ini berarti bahwa untuk biaya yang kecil, kami berpotensi menghapus beberapa ratus panggilan L
untuk string besar. Dalam kode C di atas, y
adalah set bendera jika kita secara otomatis tahu bahwa karakter ini mengarah ke string suboptimal, danz
merupakan set bendera jika kita menemukan karakter yang secara eksklusif memiliki penampilan lebih awal daripada karakter lain yang dikenal. Penampilan karakter paling awal saat ini disimpan dix
. Implementasi saat ini dari ide ini agak berantakan, tetapi kinerja hampir dua kali lipat dalam banyak hal.
Dengan dua ide ini, apa yang tidak selesai dalam satu jam sekarang membutuhkan waktu sekitar 0,015 detik.
Mungkin ada banyak trik kecil yang dapat mempercepat kinerja, tetapi pada titik ini saya mulai khawatir tentang kemampuan saya untuk bermain golf semuanya. Saya masih belum puas dengan golf, jadi saya akan kembali lagi nanti!
Pengaturan waktu
Inilah beberapa kode pengujian, yang saya undang untuk Anda coba secara 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;
}
Saya menjalankan test case OP pada laptop yang dilengkapi dengan chip Intel Core i7 1,7 GHz, dengan pengaturan optimalisasi -Ofast
. Simulasi melaporkan puncak 712KB diperlukan. Berikut ini contoh proses dari setiap kasus uji, dengan timing:
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
Dalam bermain golf, saya mencapai performa yang cukup signifikan, dan karena orang-orang sepertinya menyukai kecepatan kasar (0,013624 detik untuk menyelesaikan semua test case digabungkan) dari solusi 618-byte saya sebelumnya, saya akan meninggalkannya di sini untuk referensi:
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;}
Algoritme itu sendiri tidak berubah, tetapi kode baru bergantung pada divisi dan beberapa operasi bitwise rumit yang akhirnya memperlambat semuanya.