Проблема:
Лексикографически наименее круглая подстрока - это проблема нахождения вращения струны, имеющей самый низкий лексикографический порядок из всех таких вращений. Например, лексикографически минимальное вращение «bbaaccaadd» будет «aaccaaddbb».
Решение:
Алгоритм AO (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 строки длина н / 2 (н / 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)
}