Problema:
A substring Lexicographically menos circular é o problema de encontrar a rotação de uma corda que possui a ordem lexicográfica mais baixa de todas essas rotações. Por exemplo, a rotação lexicograficamente mínima de "bbaaccaadd" seria "aaccaaddbb".
Solução:
O algoritmo AO (n) time foi proposto por Jean Pierre Duval (1983).
Dados dois índices i
e j
, o algoritmo de Duval compara segmentos de cadeia de comprimento j - i
começando em i
e j
(chamado de "duelo" ). Se index + j - i
for maior que o comprimento da sequência, o segmento é formado ao redor.
Por exemplo, considere s = "baabbaba", i = 5 ej = 7. Como j - i = 2, o primeiro segmento começando em i = 5 é "ab". O segundo segmento começando em j = 7 é construído ao redor e também é "ab". Se as cadeias são lexicograficamente iguais, como no exemplo acima, escolhemos a que começa em i como a vencedora, que é i = 5.
O processo acima foi repetido até termos um único vencedor. Se a sequência de entrada tiver um comprimento ímpar, o último caractere vence sem uma comparação na primeira iteração.
Complexidade do tempo:
A primeira iteração compara n seqüências cada de comprimento 1 (comparações n / 2), a segunda iteração pode comparar n / 2 seqüências de comprimento 2 (comparações n / 2) e assim por diante, até que a i-ésima iteração compara duas seqüências de caracteres de comprimento n / 2 (comparações n / 2). Como o número de vencedores é dividido pela metade a cada vez, a altura da árvore de recursão é log (n), fornecendo assim um algoritmo O (n log (n)). Para n pequeno, isso é aproximadamente O (n).
A complexidade do espaço também é O (n), pois na primeira iteração, temos que armazenar n / 2 vencedores, segunda iteração n / 4 vencedores e assim por diante. (A Wikipedia afirma que esse algoritmo usa espaço constante, não entendo como).
Aqui está uma implementação do Scala; fique à vontade para converter para sua linguagem de programação favorita.
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)
}