Problème:
La sous-chaîne le moins circulaire lexicographiquement est le problème de trouver la rotation d'une chaîne possédant l'ordre lexicographique le plus bas de toutes ces rotations. Par exemple, la rotation lexicographiquement minimale de "bbaaccaadd" serait "aaccaaddbb".
Solution:
L'algorithme temporel AO (n) a été proposé par Jean Pierre Duval (1983).
Étant donné deux indices i
et j
, l'algorithme de Duval compare des segments de chaîne de longueur j - i
commençant à i
et j
(appelé un "duel" ). Si index + j - i
est supérieur à la longueur de la chaîne, le segment est formé par enroulement.
Par exemple, considérons s = "baabbaba", i = 5 et j = 7. Puisque j - i = 2, le premier segment commençant à i = 5 est "ab". Le deuxième segment commençant à j = 7 est construit par enroulement et est également "ab". Si les chaînes sont égales lexicographiquement, comme dans l'exemple ci-dessus, nous choisissons celle commençant par i comme gagnante, qui est i = 5.
Le processus ci-dessus s'est répété jusqu'à ce que nous ayons un seul gagnant. Si la chaîne d'entrée est de longueur impaire, le dernier caractère gagne sans comparaison lors de la première itération.
Complexité temporelle:
La première itération compare n chaînes de longueur 1 chacune (n / 2 comparaisons), la deuxième itération peut comparer n / 2 chaînes de longueur 2 (n / 2 comparaisons), et ainsi de suite, jusqu'à ce que la ième itération compare 2 chaînes de longueur n / 2 (comparaisons n / 2). Comme le nombre de gagnants est divisé par deux à chaque fois, la hauteur de l'arbre de récursivité est log (n), nous donnant ainsi un algorithme O (n log (n)). Pour les petits n, c'est approximativement O (n).
La complexité de l'espace est également O (n), car dans la première itération, nous devons stocker n / 2 gagnants, la deuxième itération n / 4 gagnants, etc. (Wikipedia prétend que cet algorithme utilise un espace constant, je ne comprends pas comment).
Voici une implémentation Scala; n'hésitez pas à vous convertir à votre langage de programmation préféré.
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)
}