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;}
Và ở đây, nó được làm sáng tỏ, cho "khả năng đọc":
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;
}
Thưa quý vị, tôi đã phạm một sai lầm khủng khiếp. Nó từng đẹp hơn ... Và ít goto hơn ... Ít nhất là bây giờ thì nhanh .
Chúng tôi định nghĩa một hàm đệ quy L
lấy đầu vào là một mảng s
các ký tự và số n
chuỗi. Hàm này đưa ra chuỗi kết quả thành thiết bị xuất chuẩn và trả về kích thước theo các ký tự của chuỗi đó.
Tiếp cận
Mặc dù mã bị sai lệch, chiến lược ở đây không quá phức tạp. Chúng tôi bắt đầu với một thuật toán đệ quy khá ngây thơ, mà tôi sẽ mô tả bằng mã giả:
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
Bây giờ, thuật toán này tự nó khá tàn bạo (nhưng có thể phù hợp với khoảng ~ 230 byte, tôi đã tìm thấy). Đây không phải là cách một người có được kết quả nhanh chóng. Thuật toán này quy mô cực kỳ kém với độ dài chuỗi. Thuật toán này , tuy nhiên, quy mô khá tốt với số lượng chuỗi lớn hơn. Trường hợp thử nghiệm cuối cùng sẽ được giải quyết gần như ngay lập tức, vì không có chuỗi s
nào có bất kỳ ký tự c
chung nào. Có hai thủ thuật chính tôi đã thực hiện ở trên dẫn đến tăng tốc độ đáng kinh ngạc:
Tại mỗi cuộc gọi đến L
, hãy kiểm tra xem trước đây chúng tôi có được cung cấp đầu vào tương tự không. Vì thông tin trong thực tế được truyền qua các con trỏ tới cùng một chuỗi, chúng tôi thực sự không phải so sánh các chuỗi, chỉ các vị trí, điều này thật tuyệt. Nếu chúng tôi thấy rằng chúng tôi đã nhận được thông tin này trước đây, thì không cần phải chạy qua các tính toán (hầu hết thời gian, nhưng việc có được đầu ra làm cho điều này phức tạp hơn một chút) và chúng tôi có thể thoát khỏi chỉ bằng cách trả về độ dài. Nếu chúng tôi không tìm thấy kết quả khớp, hãy lưu bộ đầu vào / đầu ra này để so sánh với các cuộc gọi trong tương lai. Trong mã C, for
vòng lặp thứ hai cố gắng tìm kết quả khớp với đầu vào. Các con trỏ đầu vào đã biết được lưu vào R
và các giá trị đầu ra ký tự và độ dài tương ứng được lưu trữ trongA
. Kế hoạch này có tác động mạnh mẽ đến thời gian chạy, đặc biệt là với các chuỗi dài hơn.
Mỗi lần chúng tôi tìm thấy địa điểm c
ở s
, có một cơ hội chúng tôi biết ngay lập tức rằng những gì chúng tôi tìm thấy không tối ưu. Nếu mọi vị trí c
xuất hiện sau một số vị trí đã biết của một chữ cái khác, chúng tôi sẽ tự động biết rằng điều này c
không dẫn đến một chuỗi con tối ưu, bởi vì bạn có thể ghép thêm một chữ cái trong đó. Điều này có nghĩa là với một chi phí nhỏ, chúng tôi có khả năng có thể loại bỏ hàng trăm cuộc gọi đến L
các chuỗi lớn. Trong mã C ở trên, y
là một cờ được đặt nếu chúng ta tự động biết rằng ký tự này dẫn đến một chuỗi dưới tối ưu và z
là một cờ được đặt nếu chúng ta tìm thấy một ký tự xuất hiện sớm hơn bất kỳ ký tự nào được biết. Sự xuất hiện sớm nhất hiện tại của các ký tự được lưu trữ trongx
. Việc thực hiện ý tưởng này hiện tại hơi lộn xộn, nhưng hiệu suất gần gấp đôi trong nhiều trường hợp.
Với hai ý tưởng này, những gì không hoàn thành trong một giờ giờ mất khoảng 0,015 giây.
Có thể có nhiều thủ thuật nhỏ hơn có thể tăng tốc hiệu suất, nhưng tại thời điểm này, tôi bắt đầu lo lắng về khả năng chơi gôn của mình. Tôi vẫn không hài lòng với golf, vì vậy tôi có thể sẽ quay lại vấn đề này sau!
Thời gian
Đây là một số mã thử nghiệm mà tôi mời bạn dùng thử trực tuyến :
#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;
}
Tôi đã chạy các trường hợp thử nghiệm của OP trên máy tính xách tay được trang bị chip Intel Core i7 1,7 GHz, với cài đặt tối ưu hóa -Ofast
. Mô phỏng báo cáo mức cao nhất là 712KB. Đây là một ví dụ chạy của từng trường hợp thử nghiệm, với thời gian:
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
Trong chơi gôn, tôi đã đạt được hiệu suất khá đáng kể và vì mọi người dường như thích tốc độ vũ phu (0,013624 giây để hoàn thành tất cả các trường hợp thử nghiệm kết hợp) của giải pháp 618 byte trước đây của tôi, tôi sẽ để nó ở đây để tham khảo:
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;}
Thuật toán tự nó không thay đổi, nhưng mã mới dựa trên các phép chia và một số thao tác bitwise phức tạp hơn, kết thúc làm chậm toàn bộ sự việc.