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общих символов . Я реализовал два основных трюка, которые привели к невероятному увеличению скорости:
При каждом вызове Lпроверяйте, получали ли мы такой же ввод раньше. Поскольку на практике информация передается через указатели на один и тот же набор строк, нам на самом деле не нужно сравнивать строки, а просто места, и это здорово. Если мы обнаружим, что мы получили эту информацию раньше, нет необходимости выполнять расчеты (большую часть времени, но получение результатов делает это немного более сложным), и мы можем избежать простого возврата длины. Если мы не найдем соответствия, сохраните этот набор ввода / вывода для сравнения с будущими вызовами. В коде C второй forцикл пытается найти совпадения с входом. Известные входные указатели сохраняются R, а соответствующие значения длины и вывода символов сохраняются вA, Этот план оказал сильное влияние на время выполнения, особенно с более длинными строками.
Каждый раз, когда мы находим местоположения cв s, есть шанс, что мы сразу узнаем, что то, что мы нашли, не является оптимальным. Если каждое местоположение cпоявляется после некоторого известного местоположения другой буквы, мы автоматически знаем, что это cне приводит к оптимальной подстроке, потому что вы можете поместить в нее еще одну букву. Это означает, что за небольшую плату мы можем удалить несколько сотен вызовов Lдля больших строк. В приведенном выше коде C yустановлен флаг, если мы автоматически знаем, что этот символ приводит к неоптимальной строке, и zустановлен флаг, если мы находим символ, который имеет только более ранние появления, чем любой другой известный символ. Текущие ранние появления символов хранятся вx, Нынешняя реализация этой идеи немного грязная, но во многих случаях производительность почти удвоилась.
С этими двумя идеями, что не закончилось через час, теперь заняло около 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;
}
Я запускал тестовые наборы OP на ноутбуке, оснащенном 1,7-ГГц чипом Intel Core i7 с настройкой оптимизации -Ofast. Моделирование сообщило, что требуется пик в 712 КБ. Вот пример выполнения каждого тестового примера с таймингами:
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
В гольфе я значительно улучшил производительность, и, поскольку людям, похоже, понравилась грубая скорость (0,013624 с для завершения всех тестовых случаев вместе) моего предыдущего 618-байтового решения, я оставлю это здесь для справки:
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;}
Сам алгоритм не изменился, но новый код опирается на деления и некоторые хитрые побитовые операции, которые в итоге замедляют процесс.