復習一下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)