复习一下KMP算法
KMP的主要思想是利用字符串自身的前缀后缀的对称性,来构建next数组,从而实现用接近O(N)的时间复杂度完成字符串的匹配
对于一个字符串str,next[j] = k 表示满足str[0...k-1] = str[j-k...j-1]的最大的k,即对于子串str[0...j-1],前k个字母等于后k个字母
现在求解str的next数组:
初始化:next[0] = -1
那么在知道了next[j]的情况下,如何递推地求出next[j+1]呢?分两种情况(令k=next[j]):
1、如果str[j]==str[k],则next[j+1] = k+1
如下图所示,对于str[0...j-1],前k个字母等于后k个字母(两个绿色部分相等),然后str[k]刚好是前k个字母的下一个字母(第一个红色)
如果str[j]==str[k],说明对于str[0...j],前k+1个字母等于后k+1个字母(绿色+红色=绿色+红色),即等于next[j]+1(绿色长度为k,红色长度为1)
2、如果str[j]!=str[k],则k=next[k],然后继续循环(回到1),直到k=-1
因为str[j]!=str[k](下图中紫色和红色不相等),所以前k+1个字母不再等于后k+1个字母了
但是由于前k个字母还是等于后k个字母(图中两个黑色虚线框住部分),所以对于任意的k‘<k,str[k-k‘...k-1]=str[j-k‘...j-1](图中第二个和最后一个绿色相等)
而next[k]表示str[0...k-1]内部的对称情况,所以令k‘=next[k],则对于str[0...k-1],前k‘个字母等于后k‘个字母(图中第一个和第二个绿色相等)
由于图中第二个绿色始终=第四个绿色,所以第一个绿色等于第四个绿色
因此将k=next[l]继续带入循环,回到判断1:
如果str[k‘]=str[j],则满足前k‘+1个字母等于后k‘+1个字母(两个浅黄色区域相等),所以next[j+1] = k‘+1;
否则,继续k‘=next[k‘]继续循环,直到k‘=-1说明已经到达第一个元素,不能继续划分,next[j+1]=0
得到了求next数组的递推方法后,现在用C++实现
void getNext( string str, int next[] ) { int len = str.length(); next[0] = -1; int j = 0, k = -1; while( j<len-1 ) { if( k==-1 || str[j]==str[k] ) next[++j] = ++k; else k = next[k]; } }
这里解释一下:由于每一轮赋值完next[j]后,k要不然是-1,要不然是next[j](上次匹配的前缀的下一个位置)
如果k=-1,说明next[j+1]=0=k+1;否则如果str[j]==str[k],说明前k+1个字母等于后k+1个字母,直接next[j+1]=k+1
所以循环中if后的语句为“next[++j] = ++k;”
现在用求解next数组的思路解决leetcode上的一道题
https://leetcode.com/problems/repeated-substring-pattern/
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:
Input: "abcabcabcabc" Output: True Explanation: It‘s the substring "abc" four times. (And the substring "abcabc" twice.)
假设str长度为len,重复的子串长度为k,则如果真的由连续多个长度为k的子串重复构成str,那么在对str求next时,由于连续对称性(如图,前后两个虚线框内字符串相等),会从next[k+1]开始,1,2,3...地递增,直到next[len]=len-k,且(len-k)%k==0,表示有整数个k
要一直求到next[len]而不是next[len-1],是因为next[len-1]只是表示前len-1个字母的内部对称性,而没有考虑到最后一个字母即str[len-1]
所以求解很简单:先对str求next数组,一直求到next[len],然后看看next[len]是否非零且整除k(k=len-next[len])
bool repeatedSubstringPattern(string str) { int len = str.length(); int next[len+1]; next[0] = -1; int j = 0, k = -1; while( j<len ) { if( k==-1 || str[j]==str[k] ) next[++j] = ++k; else k = next[k]; } return next[len]&&next[len]%(len-next[len])==0; }
时间复杂度只有O(N),而暴力需要O(N^2)