【題 目 】
給定兩個字符串str1和 str2,返回兩個字符串的最長公共子串。
【舉 例 】
str1=」1AB2345CD」, str2=」12345EF」,返回」2345″。
【要 求 】
如 果 str1長 度 爲 M , str2長 度 爲 N , 實現時間複雜度爲 O ( M x N ),額外空間複雜度爲
0(1)的方法。
【難 度 】
★ ★ ★ ☆面試
解答算法
經典動態規劃的方法能夠作到時間複雜度爲 O ( M*N ),額外空間複雜度O( M*N ),通過優化以後的實現能夠把額外空間複雜度從O( M*N )降至 0 (1 ),咱們先來介紹經典方法。
首先須要生成動態規劃表。生成大小爲 M*N 的矩陣dp ,行數爲M, 列數爲 N 。 dp[i][j]的含義是,在必須把str1[i]和 str2[j]看成公共子串最後一個字符的狀況下,公共子串最長能有多長。好比,str1=」A1234B」, str2=」CD1234″, dp[3][4]的含義是在必須把 str1[3]看成公共子串最後一個字符的狀況下,公共子串最長能有多長。這種狀況下的最長公共子串爲」1 2 3 「,所 以 dp[3][4]爲 3。再如, str1=」A12E4B」, str2=」CD12F4″,dp[3][4]的含義是在必須把str1[3]( 即’E ‘) 和 str2[4]( 即’F’)看成公共子串最後一個字符的狀況下,公共子串最長能有多長。這種狀況下根本不能構成公共子串,所 以 dp[3][4]爲 0。post
介紹了 dp[i][j]的意義後,接下來介紹dp[i][j]怎麼求。具體過程以下:優化
- 矩 陣 dp第一列即dp[0..M-1][0]。對某一個位置(i,0)來講,若是 str1[i]=str2[0] , 令dp[i][0]=1, 不然令 dp[i][0]=0。好比 str1=」ABAC」, ,str2[0]=」A」。dp 矩陣第一列上的值依次爲 dp[0][0]=1, dp[1][0]=0, dp[2][0]=1, dp[3][0]=0
- 矩 陣 d p 第 一 行 即 dp[0][0..N-1]與 步 驟 1 同理。對某一個位置(0,j)來 說 ,若是str1[0]== str2[j] . 令 dp[0][j]=1, 不然令 dp[0][j]=0。
- 其餘位置按照從左到右,再從上到下來計算,dp[i][j]的值只可能有兩種狀況。
• 如 果 str1[i]!=str2[j],說明在必須把str1[i]和 str2[j] 看成公共子串最後一個字符是不 可能的, 令 dp[i][j]=0。
• 如 果 str1[i]==str2[j],說 明 str1[i]和 str2[j]能夠做爲公共子串的最後一個字符,從最 後 一 個 字 符 向 左 能 擴 多 大 的 長 度 呢 ? 就 是 dp [i-1][j-1]的 值 , 所 以 令dp[i][j]=dp[i-1][j-1]+1
若是 str1=」abcde」,str2=」bebcd」, 計算的 dp 矩陣以下:
public static int[][] getdp(char[] str1, char[] str2) { int[][] dp = new int[str1.length][str2.length]; for (int i = 0; i < str1.length; i++) { if (str1[i] == str2[0]) { dp[i][0] = 1; } } for (int j = 1; j < str2.length; j++) { if (str1[0] == str2[j]) { dp[0][j] = 1; } } for (int i = 1; i < str1.length; i++) { for (int j = 1; j < str2.length; j++) { if (str1[i] == str2[j]) { dp[i][j] = dp[i - 1][j - 1] + 1; } } } return dp; }
生成動態規劃表dp以後,獲得最長公共子串是很是容易的。好比,上邊生成的dp中,最 大 值 是 dp[3][4]==3,說明最長公共子串的長度爲3。最長公共子串的最後一個字符是str1[3],固然也是str2[4],由於兩個字符同樣。那麼最長公共子串爲從strl[3]開始向左一共3 字節的子串,即 strl[1..3],固然也是str2[2..4]。總之,遍 歷 dp找到最大值及其位置,最長公共子串天然能夠獲得。具體過程請參看以下代碼中的Icstl方法,也是整個過程的主方法。設計
public static String lcst1(String str1, String str2) { if (str1 == null || str2 == null || str1.equals("") || str2.equals("")) { return ""; } char[] chs1 = str1.toCharArray(); char[] chs2 = str2.toCharArray(); int[][] dp = getdp(chs1, chs2); int end = 0; int max = 0; for (int i = 0; i < chs1.length; i++) { for (int j = 0; j < chs2.length; j++) { if (dp[i][j] > max) { end = i; max = dp[i][j]; } } } return str1.substring(end - max + 1, end + 1); }
經典動態規劃的方法須要大小爲MxN的 dp矩陣,但其實是能夠減少至o(1)的,由於咱們注意到計算每個dp[i][j]的時候,最多隻須要其左上方d p[i-l][j-l]的值,因此按照斜線方向來計算全部的值,只須要一個變量就能夠計算出全部位置的值,如 圖 所示3d
每一條斜線在計算以前生成整型變量len, len表示左上方位置的值,初 始 時 len=0。從斜線最左上的位置幵始向右下方依次計算每一個位置的值,假設計算到位置(i,j),此 時 len表示 位 置 的 值 。若是 strl [i]=str2[j],那麼位置( i-1,j-1 )的值爲 len+l,若是 strl[i]!=str2[j] ,那麼位置(i,j)的值爲0。計算後將len更新成位置(i,j)的值,而後計算下一個位置,即(i+1,j+1) 位置的值。依次計算下去就能夠獲得斜線上每一個位置的值,而後算下一條斜線。用全局變量 max記錄全部位置的值中的最大值3 最大值出現時,用全局變量end記錄其位置便可。
具體過程請參看以下代碼中的lcst2方法code
public static String lcst2(String str1, String str2) { if (str1 == null || str2 == null || str1.equals("") || str2.equals("")) { return ""; } char[] chs1 = str1.toCharArray(); char[] chs2 = str2.toCharArray(); int row = 0; // 斜線開始位置的行 int col = chs2.length - 1; // 斜線開始位置的列 int max = 0; // 記錄最大長度 int end = 0; // 最大長度更新時,記錄子串的結尾位置 while (row < chs1.length) { int i = row; int j = col; int len = 0; // 從(i,j)開始向右下方遍歷 while (i < chs1.length && j < chs2.length) { if (chs1[i] != chs2[j]) { len = 0; } else { len++; } // 記錄最大值,以及結束字符的位置 if (len > max) { end = i; max = len; } i++; j++; } if (col > 0) { // 斜線開始位置的列先向左移動 col--; } else { // 列移動到最左以後,行向下移動 row++; } } return str1.substring(end - max + 1, end + 1); }