【算法面試】常見動態規劃算法示例1-最長公共子串問題

 

【題 目 】
給定兩個字符串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]怎麼求。具體過程以下:優化

  1. 矩 陣 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
  2. 矩 陣 d p 第 一 行 即 dp[0][0..N-1]與 步 驟 1 同理。對某一個位置(0,j)來 說 ,若是str1[0]== str2[j] . 令 dp[0][j]=1, 不然令 dp[0][j]=0。
  3.  其餘位置按照從左到右,再從上到下來計算,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);
	}
相關文章
相關標籤/搜索