最長公共子串(LCS:Longest Common Substring)

最長公共子串(LCS:Longest Common Substring)是一個很是經典的面試題目,本人在樂視二面中被面試官問過,慘敗在該題目中。面試

什麼是最長公共子串算法

最長公共子串問題的基本表述爲:給定兩個字符串,求出它們之間最長的相同子字符串的長度。數組

最直接的解法就是暴力解法:遍歷全部子字符串,比較它們是否相同,而後去的相同子串中最長的那個。對於長度爲n的字符串,它子串的數量爲n(n-1)/2,假如兩個字符串長度均爲n,那麼該解法的複雜度爲O(n^4),想一想並非取出全部的子串,那麼該解法的複雜度爲O(n^3)。優化

複雜度過高,能夠進行優化,能夠利用動態規劃法(有重疊的子問題)。spa

暴力解法code

對於該問題,直接的思路就是要什麼就找什麼,要子串就要子串,要相同就比較每一個字符,要長度就計算長度,因此很容易寫出下列代碼:blog

 1 //暴力
 2     public static int longestCommonSubstring(String s1, String s2){
 3         char[] str1 = s1.toCharArray();
 4         char[] str2 = s2.toCharArray();
 5         int str1_length = str1.length;
 6         int str2_length = str2.length;
 7         if(str1_length == 0 || str2_length == 0)
 8             return 0;
 9         //最大長度
10         int maxLength = 0;
11         int compareNum = 0;
12         int start1 = -1;
13         int start2 = -1;
14         for(int i=0;i<str1_length;i++){
15             for(int j=0;j<str2_length;j++){
16                 int m = i;
17                 int n = j;
18                 //相同子串長度
19                 int length = 0;
20                 while(m < str1_length && n < str2_length){
21                     compareNum++;
22                     if(str1[m] != str2[n])
23                         break;
24                     m++;
25                     n++;
26                     length++;
27                 }
28                 if(length > maxLength){
29                     maxLength = length;
30                     start1 = i + 1;
31                     start2 = j + 1;
32                 }
33                 
34             }
35         }
36         System.out.println("比較次數" + compareNum + ",s1起始位置:" + start1 + ",s2起始位置:" + start2);        
37         return maxLength;
38     }

該思路以字符串中每一個字符做爲子串的開始,判斷以此開始的子串的相同字符所能達到的最大長度。從上述代碼來看,複雜度是O(n^2),可是在比較兩個相同開端的子串的效率不是O(1),是O(n),因此上述算法的複雜度爲O(n^3)。字符串

動態規劃-空間換時間string

上述解法回答面試官,面試官確定會讓你優化!class

咱們發現,在相同開端的子串的比較中,有不少事重複動做。好比在比較以i,j分別爲起點的子串時,有可能會進行i+1和j+1以及i+2和j+2位置的字符的比較。而以i+1,j+1分別爲起點的子串時,這些字符又被比較了一次。也就說該問題有很是類似的子問題,而子問題之間又有重疊,這就給動態規劃法創造了契機。

暴力解法是以子串開端開始尋找,如今換個思路,以相同子串的字符結尾來利用動態規劃法。

假設兩個字符串分別爲A、B,A[i]和B[j]分別表示其第i和j個字符,再假設K[i,j]表示以A[i]和B[j]結尾的子串的最大長度。那麼A,B分別再向下走一個字符,咱們能夠推斷出K[i+1,j+1]與K[i,j]之間的關係,若是A[i] == B[j],那麼K[i+1,j+1] = K[i,j] + 1;不然K[i+1,j+1] =0。而若是A[i+1]和B[j+1]相同,那麼就只要在以A[i]和B[j]結尾的最長相同子串以後分別添上這兩個字符便可,這樣就可讓長度增長一位,綜上所述,就是K[i+1,j+1] = (A[i] == B[j] ? K[i,j] + 1 : 0)的關係。

由上述K[i+1,j+1] = (A[i] == B[j] ? K[i,j] + 1 : 0)的關係,想到了使用二維數組來存儲兩個字符串之間的相同子串關係,由於K[i+1,j+1] = (A[i+1] == B[j+1] ? K[i,j] + 1 : 0)關係,只計算二維數據的最上列和最左列數值便可,其餘數值經過K[i+1,j+1] = (A[i+1] == B[j+1] ? K[i,j] + 1 : 0)可得。以下圖所示:

 代碼以下:

 1 //優化
 2     public static int longestCommonSubstring1(String s1, String s2){
 3         if(s1.length() == 0 || s2.length() == 0)
 4             return 0;
 5         char[] str1 = s1.toCharArray();
 6         char[] str2 = s2.toCharArray();
 7         int start1 = -1;
 8         int start2 = -1;
 9         int[][] results = new int[str2.length][str1.length];
10         //最大長度
11         int maxLength = 0;
12         int compareNum = 0;
13         for(int i=0;i<str1.length;i++){
14             results[0][i] = (str2[0] == str1[i] ? 1 : 0);
15             compareNum++;
16             for(int j=1;j<str2.length;j++){
17                 results[j][0] = (str1[0] == str2[j] ? 1 : 0);
18                 if(i>0 && j>0){
19                     if(str1[i] == str2[j]){
20                         results[j][i] = results[j-1][i-1] + 1;
21                         compareNum++;
22                     }
23                 }
24                 if(maxLength < results[j][i]){
25                     maxLength = results[j][i];
26                     start1 = i - maxLength + 2;
27                     start2 = j - maxLength + 2;
28                 }
29             }
30         }
31         System.out.println("比較次數" + (compareNum+str2.length) + ",s1起始位置:" + start1 + ",s2起始位置:" + start2);
32         return maxLength;
33     }

用二維數組保存計算結果,避免了重複計算,運算的時間複雜度下降到了O(n^2)。

相關文章
相關標籤/搜索