Это действительно может быть сделано за линейное время, O (n) и O (n) дополнительное пространство. Я предполагаю, что входные массивы являются символьными строками, но это не обязательно.
Наивный метод - после сопоставления k символов, которые равны, - находит символ, который не соответствует, и возвращает k-1 единицы в a , сбрасывает индекс в b , а затем начинает процесс сопоставления оттуда. Это ясно представляет наихудший случай O (n²) .
Чтобы избежать этого процесса обратного отслеживания, мы можем заметить, что возврат назад бесполезен, если мы не встретили символ b [0] при сканировании последних k-1 символов. Если мы действительно обнаружили , что характер, а затем откат к этой позиции будет только полезно, если в том , что к SIZED подстроки мы имели периодическое повторение.
Например, если мы посмотрим на подстроку "abcabc" где-то в a , а b равно «abcabd», и мы обнаружим, что последний символ b не совпадает, мы должны учитывать, что успешное совпадение может начаться со второй «a» в подстроке, и мы должны переместить наш текущий индекс в б соответственно, прежде чем продолжить сравнение.
Идея состоит в том, чтобы сделать некоторую предварительную обработку на основе строки b, чтобы зарегистрировать обратные ссылки в b , которые полезны для проверки в случае несоответствия. Так, например, если b - «acaacaacd», мы могли бы идентифицировать эти обратные ссылки на основе 0 (ставить под каждым символом):
index: 0 1 2 3 4 5 6 7 8
b: a c a a c a a c d
ref: 0 0 0 1 0 0 1 0 5
Например, если у нас есть равны «acaacaaca» первое несоответствие происходит на последний символ. Приведенная выше информация затем указывает алгоритму вернуться в b к индексу 5, поскольку «acaac» является распространенным. А потом только с изменением текущего индекса в б мы можем продолжить согласование по текущему индексу а . В этом примере совпадение с последним символом завершается успешно.
При этом мы можем оптимизировать поиск и убедитесь , что индекс в всегда может прогрессировать вперед.
Вот реализация этой идеи в JavaScript с использованием только самого основного синтаксиса этого языка:
function overlapCount(a, b) {
// Deal with cases where the strings differ in length
let startA = 0;
if (a.length > b.length) startA = a.length - b.length;
let endB = b.length;
if (a.length < b.length) endB = a.length;
// Create a back-reference for each index
// that should be followed in case of a mismatch.
// We only need B to make these references:
let map = Array(endB);
let k = 0; // Index that lags behind j
map[0] = 0;
for (let j = 1; j < endB; j++) {
if (b[j] == b[k]) {
map[j] = map[k]; // skip over the same character (optional optimisation)
} else {
map[j] = k;
}
while (k > 0 && b[j] != b[k]) k = map[k];
if (b[j] == b[k]) k++;
}
// Phase 2: use these references while iterating over A
k = 0;
for (let i = startA; i < a.length; i++) {
while (k > 0 && a[i] != b[k]) k = map[k];
if (a[i] == b[k]) k++;
}
return k;
}
console.log(overlapCount("ababaaaabaabab", "abaababaaz")); // 7
Хотя есть вложенные while
циклы, они не имеют больше итераций, чем n . Это потому, что значение к строго уменьшается в while
теле и не может стать отрицательным. Это может произойти только тогда, когда k++
было выполнено много раз, чтобы дать достаточно места для такого уменьшения. Таким образом, в целом, не может быть больше казней while
тела, чем естьk++
казней, и последнее явно O (n).
Чтобы завершить, здесь вы можете найти тот же код, что и выше, но в интерактивном фрагменте: вы можете ввести свои собственные строки и увидеть результат в интерактивном режиме:
function overlapCount(a, b) {
// Deal with cases where the strings differ in length
let startA = 0;
if (a.length > b.length) startA = a.length - b.length;
let endB = b.length;
if (a.length < b.length) endB = a.length;
// Create a back-reference for each index
// that should be followed in case of a mismatch.
// We only need B to make these references:
let map = Array(endB);
let k = 0; // Index that lags behind j
map[0] = 0;
for (let j = 1; j < endB; j++) {
if (b[j] == b[k]) {
map[j] = map[k]; // skip over the same character (optional optimisation)
} else {
map[j] = k;
}
while (k > 0 && b[j] != b[k]) k = map[k];
if (b[j] == b[k]) k++;
}
// Phase 2: use these references while iterating over A
k = 0;
for (let i = startA; i < a.length; i++) {
while (k > 0 && a[i] != b[k]) k = map[k];
if (a[i] == b[k]) k++;
}
return k;
}
// I/O handling
let [inputA, inputB] = document.querySelectorAll("input");
let output = document.querySelector("pre");
function refresh() {
let a = inputA.value;
let b = inputB.value;
let count = overlapCount(a, b);
let padding = a.length - count;
// Apply some HTML formatting to highlight the overlap:
if (count) {
a = a.slice(0, -count) + "<b>" + a.slice(-count) + "</b>";
b = "<b>" + b.slice(0, count) + "</b>" + b.slice(count);
}
output.innerHTML = count + " overlapping characters:\n" +
a + "\n" +
" ".repeat(padding) + b;
}
document.addEventListener("input", refresh);
refresh();
body { font-family: monospace }
b { background:yellow }
input { width: 90% }
a: <input value="acacaacaa"><br>
b: <input value="acaacaacd"><br>
<pre></pre>
b[1] to b[d]
а затем перейти к массивуa
вычислить хеш,a[1] to a[d]
если он совпадает, то это ваш ответ, если не вычислить хеш дляa[2] to a[d+1]
повторного использования хеша, вычисленного дляa[1] to a[d]
. Но я не знаю, могут ли объекты в массиве быть пригодными для вычисления на них хеш-кода.