Це дійсно можна зробити в лінійному часі, додатковому просторі O (n) та O (n) . Я припускаю, що вхідні масиви є символьними рядками, але це не суттєво.
Наївний метод - після зіставлення k рівних знаків k - знайде символ, який не відповідає, і повернеться назад k-1 одиниці в a , скине індекс у b , а потім розпочнеть процес узгодження звідти. Це явно являє собою O (n²) найгірший випадок.
Щоб уникнути цього процесу зворотного відстеження, ми можемо зауважити, що повернення назад не є корисним, якщо ми не стикалися з символом b [0] під час сканування останніх символів k-1 . Якщо ми дійсно виявили , що характер, а потім відкат до цієї позиції буде тільки корисно, якщо в тому , що до SIZED підрядка ми мали періодичне повторення.
Наприклад, якщо ми подивимось на підрядку "abcabc" десь у a , а b - "abcabd", і виявимо, що кінцевий символ b не відповідає, ми повинні врахувати, що успішний матч може розпочатися з другого "a" в підрядку, і нам слід відповідно перемістити наш поточний індекс в b назад, перш ніж продовжувати порівняння.
Тоді ідея полягає в тому, щоб зробити деяку попередню обробку на основі рядка 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" є загальним. І тоді, лише змінюючи поточний індекс в b, ми можемо продовжувати відповідність за поточним індексом a . У цьому прикладі збіг остаточного персонажа вдається.
Завдяки цьому ми можемо оптимізувати пошук і переконатися, що індекс у a завжди може просуватися вперед.
Ось реалізація цієї ідеї в 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 . Це тому, що значення k суворо зменшується в 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]
. Але я не знаю, чи об'єкти в масиві придатні для обчислення хешируемого хешу.