1. 程式人生 > >動態規劃----最長公共子序列(LCS)問題

動態規劃----最長公共子序列(LCS)問題

生成 現在 public add tostring -s 序列 highlight arr

題目:

  求解兩個字符串的最長公共子序列。如 AB34C 和 A1BC2 則最長公共子序列為 ABC。

  思路分析:可以用dfs深搜,這裏使用到了前面沒有見到過的雙重循環遞歸。也可以使用動態規劃,在建表的時候一定要註意初始化以及在發現規律的時候一定要想怎麽利用前面已經算過的結果來得到現在的結果,或者利用其他的一些規律來發現能夠解題的規律。

    技術分享圖片 

  圖中單元格需要填上相應的數字(這個數字就是dp[i][j]的定義,記錄的LCS的長度值)。可以發現規律,簡單來說:如果橫豎(i,j)對應的兩個元素相等,該格子的值 = c[i-1,j-1] + 1。如果不等,取c[i-1,j] 和 c[i,j-1]的最大值。

  當得到完整的DP表之後,我們可以通過倒推來得到相應的子序列,有時S1和S2的LCS並不是只有1個,本題並不是著重說要輸出兩個序列的所有LCS,只是要輸出其中一個LCS。

  代碼:

import java.util.ArrayList;

public class LCS {
	public static void main(String[] args) {
		ArrayList ans = dfs("AB34C", "A1BC2");
		System.out.println(ans);  // 輸出 [A, B, C]
		System.out.println(dfs("3563243", "513141"));  // 輸出 [5, 3, 4]
		System.out.println(solution("3069248", "513164318"));  // 輸出 [3, 6, 4, 8]
		System.out.println(solution("123", "456"));      // 輸出為空

	}

	// 雙重循環遞歸
	static ArrayList<Character> dfs(String s1, String s2) {
		int len1 = s1.length();
		int len2 = s2.length();
		ArrayList<Character> ans = new ArrayList<>();
		for (int i = 0; i < len1; i++) {
			// 求以i字符開頭的公共子序列
			ArrayList<Character> list = new ArrayList<>();
			// 和s2的每個字符比較
			for (int j = 0; j < len2; j++) {
				if (s1.charAt(i) == s2.charAt(j)) {// 如果相同
					list.add(s1.charAt(i));
					list.addAll(dfs(s1.substring(i + 1), s2.substring(j + 1)));
					break;
				}
			}
			if (list.size() > ans.size()) {
				ans = list;
			}
		}
		return ans;
	}

	/**
	 * 生成動規表
	 */
	static String solution(String s1, String s2) {
		int len1 = s1.length();
		int len2 = s2.length();
		int[][] dp = new int[len1 + 1][len2 + 1]; // 動規數組
		int flag = 0;
		// 初始化第一列
		// O(M)
		for (int i = 1; i <= len1; i++) {
			if (flag == 1) {
				dp[i][1] = 1;
			} else if (s1.charAt(i - 1) == s2.charAt(0)) {
				dp[i][1] = 1;
				flag = 1;
			} else {
				dp[i][1] = 0;
			}
		}

		flag = 0;
		// 初始化第一行
		// O(N)
		for (int j = 1; j <= len2; j++) {
			if (flag == 1) {
				dp[1][j] = 1;
			} else if (s2.charAt(j - 1) == s1.charAt(0)) {
				dp[1][j] = 1;
				flag = 1;
			} else {
				dp[1][j] = 0;
			}
		}
		// O(M*N)
		for (int i = 2; i <= len1; i++) { // M
			for (int j = 2; j <= len2; j++) { // N
				int maxOfLeftAndUp = Math.max(dp[i - 1][j], dp[i][j - 1]);
				if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
					// dp[i][j] = Math.max(maxOfLeftAndUp, dp[i - 1][j - 1] + 1);
					dp[i][j] = dp[i - 1][j - 1] + 1;// 這樣也是對的……
				} else {
					dp[i][j] = maxOfLeftAndUp;
				}
			}
		}
		return parseDp(dp, s1, s2);
	}

	/**
	 * 解析動態規劃表,得到最長公共子序列
	 */
	private static  String parseDp(int[][] dp, String s1, String s2) {
		int M = s1.length();
		int N = s2.length();
		StringBuilder sb = new StringBuilder();
		while (M > 0 && N > 0) {
			// 比左和上大,一定是當前位置的字符相等
			if (dp[M][N] > Math.max(dp[M - 1][N], dp[M][N - 1])) {
				sb.insert(0, s1.charAt(M - 1));
				M--;
				N--;
			} else { // 一定選擇的是左邊和上邊的大者
				if (dp[M - 1][N] > dp[M][N - 1]) {
					M--; // 往上移
				} else {
					N--; // 往左移
				}
			}
		}

		return sb.toString();
	}
}

  

  

動態規劃----最長公共子序列(LCS)問題