Problema:
La sottostringa lessicograficamente meno circolare è il problema di trovare la rotazione di una stringa che possiede l'ordine lessicografico più basso di tutte queste rotazioni. Ad esempio, la rotazione lessicograficamente minima di "bbaaccaadd" sarebbe "aaccaaddbb".
Soluzione:
L'algoritmo AO (n) time è stato proposto da Jean Pierre Duval (1983).
Dati due indici i
e j
, l'algoritmo di Duval confronta i segmenti di stringa di lunghezza a j - i
partire da i
e j
(chiamato "duello" ). Se index + j - i
è maggiore della lunghezza della stringa, il segmento viene formato avvolgendolo.
Ad esempio, considera s = "baabbaba", i = 5 e j = 7. Poiché j - i = 2, il primo segmento che inizia con i = 5 è "ab". Il secondo segmento che inizia con j = 7 è costruito avvolgendosi ed è anche "ab". Se le stringhe sono lessicograficamente uguali, come nell'esempio sopra, scegliamo quello che inizia da i come vincitore, ovvero i = 5.
Il processo sopra descritto si è ripetuto fino a quando non avremo un solo vincitore. Se la stringa di input ha una lunghezza dispari, l'ultimo carattere vince senza confronto nella prima iterazione.
Complessità temporale:
La prima iterazione confronta n stringhe ciascuna di lunghezza 1 (n / 2 confronti), la seconda iterazione può confrontare n / 2 stringhe di lunghezza 2 (n / 2 confronti) e così via, fino a quando l'i-iterazione confronta 2 stringhe di lunghezza n / 2 (n / 2 confronti). Poiché il numero di vincitori viene dimezzato ogni volta, l'altezza dell'albero di ricorsione è log (n), dandoci così un algoritmo O (n log (n)). Per n piccolo, questo è approssimativamente O (n).
Anche la complessità dello spazio è O (n), poiché nella prima iterazione, dobbiamo memorizzare n / 2 vincitori, seconda iterazione n / 4 vincitori e così via. (Wikipedia afferma che questo algoritmo utilizza uno spazio costante, non capisco come).
Ecco un'implementazione di Scala; sentiti libero di convertire il tuo linguaggio di programmazione preferito.
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)
}