459. Repeated Substring Pattern

来源:互联网 发布:linux下grub引导win7 编辑:程序博客网 时间:2024/05/16 15:15

Given a non-empty string check if it can be constructed by taking a substring of it and appending multiple copies of the substring together. You may assume the given string consists of lowercase English letters only and its length will not exceed 10000.

Example 1:

Input: "abab"Output: TrueExplanation: It's the substring "ab" twice.

Example 2:

Input: "aba"Output: False

Example 3:

Input: "abcabcabcabc"Output: TrueExplanation: It's the substring "abc" four times. (And the substring "abcabc" twice.)

code:

discuss中的答案:

First, we build the KMP table.

  1. Roughly speaking, dp[i+1] stores the maximum number of characters that the string is repeating itself up to position i.
  2. Therefore, if a string repeats a length 5 substring 4 times, then the last entry would be of value 15.
  3. To check if the string is repeating itself, we just need the last entry to be non-zero and str.size() to divide (str.size()-last entry).

class Solution {public:    bool repeatedSubstringPattern(string str) {        int i = 1, j = 0, n = str.size();        vector<int> dp(n+1,0);        while( i < str.size() ){            if( str[i] == str[j] ) dp[++i]=++j;            else if( j == 0 ) i++;            else j = dp[j];        }        return dp[n]&&dp[n]%(n-dp[n])==0;    }};


KMP看毛片算法:


kenby

kmp算法的理解

    KMP算法曾被我戏称为看毛片算法,当时笑喷......大三那个时候硬着头皮把算法导论的kmp算法啃完,弄懂了kmp算法

的原理,甚至还写出了代码,这几天再次温习的时候,发现忘得比较彻底。我总结,学算法不能只对着书本学理论,而应该

用自己的理解去看清算法的本质,最好用文字把你的理解记录下来,这样才能做到活学活用,而且不容易忘。写这篇博客就是想把自己这几天的思路记下来。

 

一 kmp算法为什么比传统的字符串匹配算法快

假设文本T = y1y2y3....yn, 模式 P = p1p2p3...pm, 传统的匹配算法把位移为0,1,...n-m时的文本依次跟P比较,每次比较最多花费O(m)的时间,算法的复杂度为O((n-m+1)*m)。这种算法没有利用匹配过的信息,每次都从头开始比较,速度很慢。而kmp算法充分利用了之前的匹配信息,从而避免一些明显不合法的位移。加快匹配过程。来看一个例子:

 

#########000xxxx000######                       文本T

|<---- s ---->|000xxxx000~~~                             模式P

 

假设位移为s时,T和P匹配了红色部分的字符,即匹配到了模式P的前10个字符,如果按照传统的匹配方法,下一步就是从位移s+1开始比较,而kmp算法则直接从位移s+7开始比较,而且断定:位移s+7对应的串和模式P的前3个字符是相同的,可

以不用比较,直接从第4个字符开始比较,这种跳跃式的匹配是不是比传统匹配方法快很多,如下图所示:

 

#########000xxxx000######                       文本T

|<-------- s+7-------->| 000xxxx000~~~               模式P

 

 

 

那么kmp是如何实现这种跳跃的呢?注意到红色部分的字符,即模式P的前10个字符,有一个特点:它的开始3个字符和末尾

3个字符是一样的,又已知文本T也存在红色部分的字符,我们把位移移动 10-3 = 7个位置,让模式P的开始3个字符对准文本

T红色部分的末尾3个字符,那么它们的前3个字符必然可以匹配。

 

二 构造前缀数组

 

上面的例子是文本T和模式P匹配了前面10个字符的情况下发生的,而且我们观察到模式P的前缀P10中,它的开始3个字符和末尾3个字符是一样的。如果对于模式P的所有前缀P1,P2...Pm,都能求出它们首尾有多少个字符是一样的,当然相同的字

符数越多越好,那么就可以按照上面的方法,进行跳跃式的匹配。

 

 

定义:

Pi表示模式P的前i个字符组成的前缀, next[i] = j表示Pi中的开始j个字符和末尾j个字符是一样的,而且对于前缀Pi来说,这样

的j是最大值。next[i] = j的另外一个定义是:有一个含有j个字符的串,它既是Pi的真前缀,又是Pi的真后缀

 

规定:

next[1] = next[0] = 0

 

 

next[i]就是前缀数组,下面通过1个例子来看如何构造前缀数组。

例子1:cacca有5个前缀,求出其对应的next数组。

前缀2为ca,显然首尾没有相同的字符,next[2] = 0

前缀3为cac,显然首尾有共同的字符c,故next[3] = 1

前缀4为cacc,首尾有共同的字符c,故next[4] = 1

前缀5为cacca,首尾有共同的字符ca,故next[5] = 2

 

 

 

如果仔细观察,可以发现构造next[i]的时候,可以利用next[i-1]的结果。假设模式已求得next[10] = 3,如下图所示:

 

 

000#xxx000         前缀P10

000                        末尾3个字符

 

根据前缀函数的定义:next[10] = 3意味着末尾3个字符和P10的前3个字符是一样的

为求next[11],可以直接比较第4个字符和第11个字符,如下图所示:蓝色和绿色的#号所示,如果它们相等,则

next[11] = next[10]+1 = 4,这是因为next[10] = 3保证了前缀P11和末尾4个字符的前3个字符是一样的.

 

000#xxx000#       前缀P11

000#                      末尾4个字符

 

所以只需验证第4个字符和第11个字符。但如果这两个字符不想等呢?那就继续迭代,利用next[next[10] = next[3]的值来求

next[11]。代码如下:

 

C代码  收藏代码
  1. void compute_prefix(int *next, char *p)  
  2. {  
  3.     int     i, n, k;  
  4.   
  5.     n = strlen(p);  
  6.     next[1] = next[0] = 0;  
  7.     k = 0;      /* 第i次迭代开始之前,k表示next[i-1]的值 */    
  8.     for (i = 2; i <= n; i++) {  
  9.         for (; k != 0 && p[k] != p[i-1]; k = next[k]);  
  10.         if (p[k] == p[i-1]) k++;  
  11.         next[i] = k;  
  12.     }  
  13. }  
 

 

三 模拟KMP的查找过程

这里实现的算法与算法导论中的不一样,我觉得这种方法更加直观,思路就是模拟第1部分介绍的方法,每次匹配的时候

都利用上一次匹配信息,对于模式P,从第next[q]个字符开始比较,对于文本T,用一个变量s指示将要从哪个位置开始比较

,迭代开始之前,就从s这个位置开始。

 

C代码  收藏代码
  1. void kmp_match(char *text, char *p, int *next)  
  2. {  
  3.     int     m, n, s, q;  
  4.   
  5.     m = strlen(p);  
  6.     n = strlen(text);  
  7.     q = s = 0;  /* q表示上一次迭代匹配了多少个字符, 
  8.                    s表示这次迭代从text的哪个字符开始比较 */   
  9.     while (s < n) {  
  10.         for (q = next[q]; q < m && p[q] == text[s]; q++, s++);  
  11.         if (q == 0) s++;  
  12.         else if (q == m) {  
  13.             printf("pattern occurs with shift %d\n", s-m);  
  14.         }  
  15.     }  
  16. }  
 

四 测试

 

C代码  收藏代码
  1. int main()  
  2. {  
  3.     int     next[101], n;  
  4.     char    *p = "ca";  
  5.     char    *text = "cacca";  
  6.   
  7.     compute_prefix(next, p);  
  8.     kmp_match(text, p, next);  
  9.   
  10.     return 0;  
  11. }  
转自 http://kenby.iteye.com/blog/1025599