在O(n)中找到输入字符串的最小周期?

时间:2013-09-04 18:11:04

标签: string algorithm pattern-matching knuth-morris-pratt

鉴于以下问题:

  

定义:

     

设S是字母表上的字符串Σ。S'S的最小句点   如果S'是最小的字符串,那么:

     

S = (S')^k (S'')

     

其中S''S的前缀。如果不存在此类S',则S为。{1}}   不是定期的。

     

示例:S = abcabcabcabca。然后abcabcS = abcabc abcabc a以来的一段时间,但自abc起最短的时间段为S = abc abc abc abc a

     

给出一个算法来查找输入字符串S或的最小周期   声明S不是定期的。

     

提示:您可以在O(n) ...

中执行此操作

我的解决方案:我们使用KMP,它在O(n)中运行。

通过问题的定义,S =(S')^ k(S''),那么我认为如果我们创造 最短时间内的自动机,并找到找到最短时间的方法,然后我就完成了。

问题是在哪里放置自动机的失败箭头......

非常感谢任何想法,

此致

5 个答案:

答案 0 :(得分:0)

我不确定我理解您的尝试解决方案。 KMP是一个有用的子程序 - 最小的时期是KMP在完全匹配后移动针弦(即S)的距离。

答案 1 :(得分:0)

这个问题可以用Z函数解决,this教程可以帮到你。

答案 2 :(得分:0)

查看此解决方案是否适用于O(n)。我使用了字符串旋转。

public static int stringPeriod(String s){

    String s1= s;
    String s2= s1;

    for (int i=1; i <s1.length();i++){
        s2=rotate(s2);
        if(s1.equals(s2)){
            return i;
        }
    }

    return -1;
}

public static String rotate(String s1){

    String  rotS= s1;

    rotS = s1.substring(1)+s1.substring(0,1);

    return rotS;

}

完整的程序可在this github repository

中找到

答案 3 :(得分:0)

好的,因此这个问题可以肯定地在O(n)中解决,我们只需要按照您的建议巧妙地使用KMP。

解决最长的适当前缀(这也是一个后缀问题)是我们将使用的KMP的重要组成部分。

最长的适当前缀(这也是一个后缀问题)是一个大问题,所以现在就将其称为前缀后缀问题

前缀后缀问题可能很难理解,因此我将举一些例子。

  

“ abcabc”的前缀后缀解决方案是   “ abc”,因为这是最长的字符串,都是正确的前缀   和适当的后缀(正确的前缀和后缀不能是整个   字符串)。

     

“ abcabca”的前缀后缀解决方案是“ a”

Hmmmmmmmmm 如果我们只是从“ abcabca”结尾处截去“ a”,请等一会儿,然后留下“ abcabc”,如果我们得到新的解决方案(“ abc”)字符串并再次将其砍掉,剩下的就是“ abc” Hmmmmmmmmm 。非常有趣。(这几乎是解决方案,但我将讨论为什么如此)

好吧,让我们尝试进一步将这种直觉形式化,看看我们是否能够找到解决方案。

我将在论点中使用一个关键假设:

  

我们模式的最小周期是我们模式中每个较大周期的有效周期

让我们将模式的前i个字符的前缀后缀解决方案存储在lps[i]中。此lps数组可以在O(n)中进行计算,并且已在KMP算法中使用,您可以在https://www.geeksforgeeks.org/kmp-algorithm-for-pattern-searching/ O(n)中进行计算的更多信息。 >

很显然,我将列出一些lps数组的示例

  

模式:“ aaaaa”

     

lps:[0、1、2、3、4]

     

模式:“ aabbcc”

     

lps:[0,1,0,0,0,0]

     

模式:“ abcabcabc”

     

lps:[0、0、0、1、2、3、4、5、6]

现在让我们定义一些变量,以帮助我们找出为什么这个lps数组有用。

  

l为模式的长度,让k为lps数组(k=lps[l-1])中的最后一个值

k告诉我们,字符串的前k个字符与字符串的后k个字符相同。我们可以利用这一事实找到一个时期!

使用此信息,我们现在可以显示由字符串的前l-k个字符组成的前缀形成有效期。这很清楚,因为我们如何定义k数组,因此不在前缀中的后k个字符必须与前缀中的前lps个字符匹配。我们前缀中的前k个字符必须与构成我们后缀的后k个字符相同。

在实践中,您可以通过一个简单的while循环来实现此功能,如下所示,其中index标记了您当前认为是最小周期的后缀的结尾。

public static void main(String[] args){
    String pattern="abcabcabcabca";
    int[] lps= calculateLPS(pattern);
    //start at the end of the string
    int index=lps.length-1;
    while(lps[index]!=0){
        //shift back
        index-=lps[index];
    }
    System.out.println(pattern.substring(0,index+1));
}

由于计算lps发生在O(n)中,并且您总是在while循环中至少移回了1步,因此整个过程的时间复杂度仅为O(n)

如果您想在下面查看我的确切代码,我从我的calculateLPS()方法中大量借鉴了KMP的geeksForGeeks实现,但是我建议您也看一下它们的解释:https://www.geeksforgeeks.org/kmp-algorithm-for-pattern-searching/

static int[] calculateLPS(String pat) {
    int[] lps = new int[pat.length()];
    int len = 0;
    int i = 1;
    lps[0] = 0;

    while (i < pat.length()) {
        if (pat.charAt(i) == pat.charAt(len)) {
            len++;
            lps[i] = len;
            i++;
        }
        else {
            if (len != 0) {
                len = lps[len - 1];
            }
            else {
                lps[i] = len;
                i++;
            }
        }
    }
    System.out.println(Arrays.toString(lps));
    return lps;
}

最后但并非最不重要的一点,感谢您发布了这样一个有趣的问题,弄清楚它真有趣!另外,我对此还很陌生,所以请让我知道我的解释中是否有任何疑问。

答案 4 :(得分:0)

KMP

可以轻松解决此问题
  • 将字符串连接到其自身并在其上运行KMP。
  • 让n为原始字符串的长度。
  • 在KMP数组中搜索第一个值> = n。该值必须位于k> = n(从0开始)的位置。
  • 然后k-n + 1是字符串最短周期的长度。

示例:

Original string = abaaba
n = 6
New string = abaabaabaaba
KMP values for this new string: 0 0 1 1 2 3 4 5 6 7 8 9

第一个值> = n为6,它位于位置8。8-6 + 1 = 3是字符串的最短时段(aba)的长度。