Leetcode 459.重復的子字符串
重復的子字符串
給定一個非空的字符串,判斷它是否可以由它的一個子串重復多次構成。給定的字符串只含有小寫英文字母,並且長度不超過10000。
示例 1:
輸入: "abab"
輸出: True
解釋: 可由子字符串 "ab" 重復兩次構成。
示例 2:
輸入: "aba"
輸出: False
示例 3:
輸入: "abcabcabcabc"
輸出: True
解釋: 可由子字符串 "abc" 重復四次構成。 (或者子字符串 "abcabc" 重復兩次構成。)
復習一下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++實現
1 void getNext(string str,int next[]){ 2 int len=str.length(); 3 next[0]=-1; 4 int j=0,k=-1; 5 while(j<len-1){ 6 if(k==-1||str[j]==str[k]) 7 next[++j]=++k; 8 else 9 k=next[k]; 10 } 11 }
這裏解釋一下:由於每一輪賦值完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;"
思路
假設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])
1 class Solution { 2 public boolean repeatedSubstringPattern(String s) { 3 int len=s.length(); 4 int[] next=new int[len+1]; 5 next[0]=-1; 6 int j=0,k=-1; 7 while(j<len){ 8 if(k==-1||s.charAt(j)==s.charAt(k)){ 9 next[++j]=++k; 10 }else{ 11 k=next[k]; 12 } 13 } 14 return (next[len]>0)&&next[len]%(len-next[len])==0; 15 } 16 }
Leetcode 459.重復的子字符串