Problema:
Lexicográficamente, la subcadena menos circular es el problema de encontrar la rotación de una cadena que posee el orden lexicográfico más bajo de todas esas rotaciones. Por ejemplo, la rotación lexicográficamente mínima de "bbaaccaadd" sería "aaccaaddbb".
Solución:
El algoritmo de tiempo AO (n) fue propuesto por Jean Pierre Duval (1983).
Dados dos índices i
y j
, el algoritmo de Duval compara segmentos de longitud de cadena que j - i
comienzan en i
y j
(llamado "duelo" ). Si index + j - i
es mayor que la longitud de la cadena, el segmento se forma envolviendo.
Por ejemplo, considere s = "baabbaba", i = 5 y j = 7. Como j - i = 2, el primer segmento que comienza en i = 5 es "ab". El segundo segmento que comienza en j = 7 se construye envolviendo y también es "ab". Si las cadenas son lexicográficamente iguales, como en el ejemplo anterior, elegimos el que comienza en i como ganador, que es i = 5.
El proceso anterior se repite hasta que tengamos un único ganador. Si la cadena de entrada es de longitud impar, el último carácter gana sin comparación en la primera iteración.
Complejidad del tiempo:
La primera iteración compara n cadenas de longitud 1 (n / 2 comparaciones), la segunda iteración puede comparar n / 2 cadenas de longitud 2 (n / 2 comparaciones), y así sucesivamente, hasta que la i-ésima iteración compare 2 cadenas de longitud n / 2 (n / 2 comparaciones). Dado que el número de ganadores se reduce a la mitad cada vez, la altura del árbol de recursión es log (n), lo que nos da un algoritmo O (n log (n)). Para n pequeña, esto es aproximadamente O (n).
La complejidad del espacio también es O (n), ya que en la primera iteración, tenemos que almacenar n / 2 ganadores, la segunda iteración n / 4 ganadores, y así sucesivamente. (Wikipedia afirma que este algoritmo usa espacio constante, no entiendo cómo).
Aquí hay una implementación de Scala; siéntase libre de convertir a su lenguaje de programación favorito.
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)
}