Мне нужно найти эффективный (псевдо) код для решения следующей проблемы:
Учитывая две последовательности (не обязательно различных) целых чисел (a[1], a[2], ..., a[n])
и (b[1], b[2], ..., b[n])
, найти максимальное d
такое , что a[n-d+1] == b[1]
, a[n-d+2] == b[2]
..., и a[n] == b[d]
.
Это не домашняя работа, я на самом деле придумал это, когда пытался сжать два тензора вдоль как можно большего числа измерений. Я подозреваю, что эффективный алгоритм существует (возможно O(n)
?), Но я не могу придумать что-то, чего нет O(n^2)
. O(n^2)
Подход был бы очевидной петля на , d
а затем внутренний цикл по пунктам, чтобы проверить требуемое состояние до удара максимума d
. Но я подозреваю, что лучше, чем это возможно.
b[1] to b[d]
а затем перейти к массивуa
вычислить хеш,a[1] to a[d]
если он совпадает, то это ваш ответ, если не вычислить хеш дляa[2] to a[d+1]
повторного использования хеша, вычисленного дляa[1] to a[d]
. Но я не знаю, могут ли объекты в массиве быть пригодными для вычисления на них хеш-кода.a
и началомb
. Как это .m
число элементов вa
, иn
количество элементов вb
. К сожалению, у меня недостаточно опыта работы с KMP, чтобы рассказать, как его адаптировать.Ответы:
Вы можете использовать алгоритм z, алгоритм линейного времени ( O (n) ), который:
Вам нужно объединить ваши массивы ( b + a ) и запустить алгоритм на полученном построенном массиве до первого i, такого, что Z [i] + i == m + n .
Например, для a = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0] конкатенация будет [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3], что привело бы к Z [10] = 2, при этом Z [i] + i = 12 = m + n .
источник
Для O (n) временной / пространственной сложности задача состоит в том, чтобы оценить хэши для каждой подпоследовательности. Рассмотрим массив
b
:Используя метод Хорнера , вы можете оценить все возможные хеши для каждой подпоследовательности. Выберите базовое значение
B
(больше, чем любое значение в обоих ваших массивах):Обратите внимание, что вы можете оценить каждую последовательность за O (1) раз, используя результат предыдущей последовательности, следовательно, все затраты на работу O (n).
Теперь у вас есть массив
Hb = [h(b1), h(b2), ... , h(bn)]
, гдеHb[i]
находится хэш отb1
доbi
.Сделайте то же самое для массива
a
, но с небольшим фокусом:Вы должны заметить, что при переходе от одной последовательности к другой вы умножаете всю предыдущую последовательность на B и добавляете новое значение, умноженное на B. Например:
Теперь у вас есть массив
Ha = [h(an), h(an-1), ... , h(a1)]
, гдеHa[i]
находится хэш отai
доan
.Теперь вы можете сравнить
Ha[d] == Hb[d]
всеd
значения от n до 1, если они совпадают, у вас есть свой ответ.Это означает, что две разные последовательности могут иметь одинаковый хеш, но две равные последовательности всегда будут иметь одинаковый хеш.
источник
Это действительно может быть сделано за линейное время, 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 (ставить под каждым символом):
Например, если у нас есть равны «acaacaaca» первое несоответствие происходит на последний символ. Приведенная выше информация затем указывает алгоритму вернуться в b к индексу 5, поскольку «acaac» является распространенным. А потом только с изменением текущего индекса в б мы можем продолжить согласование по текущему индексу а . В этом примере совпадение с последним символом завершается успешно.
При этом мы можем оптимизировать поиск и убедитесь , что индекс в всегда может прогрессировать вперед.
Вот реализация этой идеи в JavaScript с использованием только самого основного синтаксиса этого языка:
Хотя есть вложенные
while
циклы, они не имеют больше итераций, чем n . Это потому, что значение к строго уменьшается вwhile
теле и не может стать отрицательным. Это может произойти только тогда, когдаk++
было выполнено много раз, чтобы дать достаточно места для такого уменьшения. Таким образом, в целом, не может быть больше казнейwhile
тела, чем естьk++
казней, и последнее явно O (n).Чтобы завершить, здесь вы можете найти тот же код, что и выше, но в интерактивном фрагменте: вы можете ввести свои собственные строки и увидеть результат в интерактивном режиме:
Показать фрагмент кода
источник