ОП здесь: я принял ответ Руаха, поскольку он касается моего вопроса, но я хотел дать свое собственное объяснение другим, которые могут наткнуться на этот пост, пытаясь понять алгоритм Дювала.
Проблема:
Лексикографически наименьшая круглая подстрока является проблемой нахождения
вращение строки, имеющей самый низкий лексикографический порядок
всех таких вращений. Например, лексикографически минимальный
вращение "bbaaccaadd" будет "aaccaaddbb".
Решение:
Алгоритм времени O (n) был предложен Жаном Пьером Дювалом (1983).
С учетом двух индексов i
и j
алгоритм Дюваля сравнивает сегменты строки длиной j - i
, начиная с i
и j
(называемые "дуэль" ). Если index + j - i
больше длины строки, сегмент формируется путем обтекания.
Например, рассмотрим s = "baabbaba", i = 5 и j = 7. Поскольку j - i = 2, первый сегмент, начинающийся с i = 5, - это "ab". Второй сегмент, начинающийся с j = 7, создается путем обтекания и также является "ab".
Если строки лексикографически равны, как в приведенном выше примере, мы выбираем ту, которая начинается с i, в качестве победителя, то есть i = 5.
Вышеописанный процесс повторяется, пока у нас не будет единственного победителя. Если входная строка имеет нечетную длину, последний символ выигрывает без сравнения в первой итерации.
Сложность времени:
Первая итерация сравнивает n строк каждой длины 1 (n / 2 сравнения), вторая итерация может сравнивать n / 2 строки длины 2 (n / 2 сравнения) и т. Д., Пока i-я итерация не сравнится 2 строки длины n / 2 (n / 2 сравнения). Поскольку число победителей уменьшается вдвое, высота дерева рекурсии равна log (n), что дает нам алгоритм O (n log (n)). Для малых n это примерно O (n).
Сложность пространства также равна O (n), так как на первой итерации мы должны хранить n / 2 победителя, на второй итерации n / 4 победителя и так далее. (Википедия утверждает, что этот алгоритм использует постоянное пространство, я не понимаю, как).
Вот реализация Scala; не стесняйтесь конвертировать на ваш любимый язык программирования.
def lexicographicallyMinRotation(s: String): String = {
@tailrec
def duel(winners: Seq[Int]): String = {
if (winners.size == 1) s"${s.slice(winners.head, s.length)}${s.take(winners.head)}"
else {
val newWinners: Seq[Int] = winners
.sliding(2, 2)
.map {
case Seq(x, y) =>
val range = y - x
Seq(x, y)
.map { i =>
val segment = if (s.isDefinedAt(i + range - 1)) s.slice(i, i + range)
else s"${s.slice(i, s.length)}${s.take(s.length - i)}"
(i, segment)
}
.reduce((a, b) => if (a._2 <= b._2) a else b)
._1
case xs => xs.head
}
.toSeq
duel(newWinners)
}
}
duel(s.indices)
}