問題:
辞書式で最小の円形部分文字列は、そのようなすべての回転の辞書式順序が最も低い文字列の回転を見つける問題です。たとえば、「bbaaccaadd」の辞書式最小回転は「aaccaaddbb」になります。
解決:
AO(n)時間アルゴリズムは、Jean Pierre Duval(1983)によって提案されました。
2つのインデックスi
とを指定するとj
、Duvalのアルゴリズムj - i
はi
andで始まる長さのストリングセグメントを比較しますj
("duel"と呼ばれます)。index + j - i
が文字列の長さより大きい場合、セグメントは折り返して形成されます。
たとえば、s = "baabbaba"、i = 5、j = 7について考えます。j-i = 2なので、i = 5で始まる最初のセグメントは "ab"です。j = 7で始まる2番目のセグメントは、ラップアラウンドによって構築され、これも「ab」です。上記の例のように、文字列が辞書式に等しい場合、勝者としてiから始まるものを選択します。つまり、i = 5です。
上記のプロセスは、勝者が1人になるまで繰り返されました。入力文字列の長さが奇数の場合、最後の文字が最初の反復での比較なしで勝ちます。
時間の複雑さ:
最初の反復では、長さ1のn個の文字列を比較し(n / 2比較)、2番目の反復では、長さ2のn / 2個の文字列を比較します(n / 2比較)。長さn / 2(n / 2の比較)。勝者の数は毎回半分になるため、再帰ツリーの高さはlog(n)であり、O(n log(n))アルゴリズムが得られます。小さなnの場合、これはおよそO(n)です。
スペースの複雑さもO(n)です。最初の反復では、n / 2の勝者、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)
}