使用O(n)中的后缀数组的字符串的最小词典旋转


9

我将引用ACM 2003中的问题:

考虑一个长度为n(1 <= n <= 100000)的字符串。确定其最小词典编排旋转。例如,字符串“ alabala”的旋转为:

阿拉巴拉

拉巴拉

阿巴拉尔

巴拉拉

阿拉拉卜

拉拉巴

阿拉巴拉

其中最小的是“ aalabal”。

至于解决方案-我知道我需要构造一个后缀数组 -可以说我可以在O(n)中做到这一点。我的问题仍然是,如何找到O(n)的最小旋转度?(n =字符串的长度)

我对此问题非常感兴趣,但仍然不知如何解决。我对概念和如何解决问题更感兴趣,而不对具体实现更感兴趣。

注意:最小旋转的含义与英语词典中的顺序相同-“ dwor”在“ word”之前,因为d在w之前。

编辑:后缀数组构造需要O(N)

最后编辑:我想我找到了解决方案!!!如果我只是合并两个字符串怎么办?因此,如果字符串是“ alabala”,那么新字符串将是“ alabalaalabala”,现在我只需构造一个后缀数组(在O(2n)= O(n)中)并得到第一个后缀?我想这可能是对的。你怎么看?谢谢!


您如何定义“最小值”?使用的度量标准是什么(也许很明显,但我不是专家)?
Giorgio

感谢您的来信!我认为旋转必须是最小的(最小偏移量),而不是按字典顺序旋转的结果。
乔治

我仍然缺少一些东西:复杂性中是否包括后缀数组的构造和排序?我想象构造数组并对其进行排序所需的时间要比O(n)多。
Giorgio'1

我认为将原始字符串重复两次的想法很棒!然后您可以在O(2n)= O(n)中构建后缀数组。但是您是否不需要对其进行排序以找到最小值?这需要的比O(n)还多,对不对?
乔治

@Giorgio好,后缀数组本身包含已排序的满足条件。还有一点,也许有点题外话-别忘了即使在o(n)中也可以对排序的对象进行一些假设(例如,检查基数排序)就可以完成排序
Tomy 2012年

Answers:


5

构造长度为N的字符串的所有旋转的简单技巧是将字符串与其自身连接起来。

然后,此2N长度字符串的每个N长度子字符串都是原始字符串的旋转。

然后,使用O(N)树构造来查找“按字典顺序最小”的子字符串。


0

我很确定后缀数组中包含的信息不足以帮助您达到O(n),但是最多可以帮助您达到O(n log n)。考虑这个后缀系列:

a
aba
abacaba
abacabadabacaba
abacabadabacabaeabacabadabacaba
...

通过采用前一个后缀(例如aba),添加尚未使用的下一个字符,然后再次添加前一个后缀(所以aba-> aba c aba)来构造下一个后缀。

现在考虑这些字符串(为强调起见添加了空格,但该空间不是字符串的一部分):

ad abacaba
bd abacaba
cd abacaba

对于这三个字符串,后缀数组的开始将如下所示:

a
aba
abacaba
(other suffixes)

看起来很熟悉?当然,这些字符串是为创建此后缀数组而量身定制的。现在,根据开头字母(a,b或c),“正确”索引(解决问题的方法)是上面列表中的第一个,第二个或第三个后缀。

第一个字母的选择几乎不会影响后缀数组。特别是,它不影响后缀数组中前三个后缀的顺序。这意味着我们有log n个字符串,其后缀数组非常相似,但“正确”索引却非常不同。

尽管我没有确凿的证据,但这强烈建议我,除了比较与数组中前三个索引相对应的旋转以按字典顺序排序外,这意味着您至少需要O(n log n)的时间(因为可选第一个字符的数量-在本例中为3-为log n,比较两个字符串需要O(n)时间)。

这并不排除使用O(n)算法的可能性。我只是怀疑后缀数组是否可以帮助您达到这一运行时间。


0

最小的旋转是从后缀数组中的某些后缀开始的旋转。后缀按字典顺序排序。这为您提供了一个重要的起点:

  • 您知道一旦获得了这样的k,即以后缀k开头的旋转小于以后缀k +1 开头的旋转,就完成了(从第一个开始);
  • 可以做的比较“旋转后缀开始ķ比旋转较小的开始后缀ķ 1”通过比较后缀的长度和任选地,一种字符与另一个字符的比较在O(1)。

编辑:“一个字符与另一个字符”可能并非总是如此,它可能不止一个字符,但总的来说,您在整个搜索过程中不会检查n个以上的字符,因此它是O(n)。

简短证明:仅在后缀k +1大于后缀k时检查字符,然后停止并找到后缀k +1小于后缀k的解决方案(然后您知道后缀k是您要查找的字符)。因此,您仅在后缀(沿长度方向)上升的顺序中检查字符。由于仅检查多余的字符,因此您不能检查超过n个字符。

编辑2:该算法基于以下事实:“如果后缀数组中有两个相邻后缀,并且前一个比后一个短,则前一个是后一个的前缀”。如果不是这样,那么对不起。

EDIT3:不,它不成立。“ abaaa”具有后缀表“ a”,“ aa”,“ aaa”,“ abaaa”,“ baaa”。但是,也许这种思路最终可以导致解决方案,只是更多细节必须更加复杂。主要问题是是否有可能通过检查较少的字符来进行上述比较,所以总的是O(n),我以某种方式认为可能。我现在不知道如何。


0

问题:

从词法上讲,最小圆形子串是找到具有所有此类旋转中最低的词法顺序的串的旋转的问题。例如,按词典顺序,“ bbaaccaadd”的最小旋转为“ aaccaaddbb”。

解:

AO(n)时间算法由Jean Pierre Duval(1983)提出。

给定两个索引ij,Duval算法比较长度j - ii和的字符串段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)
}

-1

没有什么比O(N²)更好的了。

如果您有N个整数的列表,则可以选择O(N)比较中的最小整数。

在这里,您有N个大小为N的字符串的列表(构造它们不花钱,字符串完全由其起始索引确定)。您可以选择O(N)个比较中的最小者。但是每个比较都是O(N)个基本操作。因此复杂度为O(N²)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.