问题:
从词法上讲,最小圆形子串是找到具有所有此类旋转中最低的词法顺序的串的旋转的问题。例如,按词典顺序,“ bbaaccaadd”的最小旋转为“ aaccaaddbb”。
解:
AO(n)时间算法由Jean Pierre Duval(1983)提出。
给定两个索引i
和j
,Duval算法比较长度j - i
为i
和的字符串段j
(称为“决斗”)。如果index + j - i
大于字符串的长度,则该段通过环绕而形成。
例如,考虑s =“ baabbaba”,i = 5且j =7。由于j-i = 2,所以从i = 5开始的第一段是“ ab”。从j = 7开始的第二个段是通过环绕而构建的,并且也是“ ab”。如果字符串在字典上相等,如上例所示,我们选择从i开始的那一个作为赢家,即i = 5。
重复上述过程,直到我们有一个获胜者。如果输入字符串的长度为奇数,则在第一个迭代中无需比较就可以赢得最后一个字符。
时间复杂度:
第一次迭代比较每个长度为1的n个字符串(n / 2个比较),第二次迭代可以比较长度为2的n / 2个字符串(n / 2个比较),依此类推,直到第i次迭代比较2个长度为2的字符串。长度为n / 2(n / 2个比较)。由于每次获胜者的数量减半,因此递归树的高度为log(n),因此我们得到了O(n log(n))算法。对于小n,大约为O(n)。
空间复杂度也是O(n),因为在第一次迭代中,我们必须存储n / 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)
}