1. 程式人生 > >Leetcode 459.重復的子字符串

Leetcode 459.重復的子字符串

abc new font gpa 復雜度 不能 png 虛線 求解

重復的子字符串

給定一個非空的字符串,判斷它是否可以由它的一個子串重復多次構成。給定的字符串只含有小寫英文字母,並且長度不超過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.重復的子字符串