最优子结构(Optimal Substructure)
最优子结构性质是指问题的最优解可以由其子问题的最优解构造而成。换句话说,如果一个问题可以分解成若干子问题,并且这些子问题的最优解能够组合成原问题的最优解,那么这个问题就具有最优子结构性质。
最长公共子序列(LCS)
最长公共子序列问题是一个典型的具有最优子结构性质的问题。它的目标是找到两个序列的最长子序列,该子序列在两个原始序列中出现的顺序一致,但不一定是连续的。例如,序列 "ABCBDAB" 和 "BDCABA" 的最长公共子序列是 "BDAB"。
动态规划解决方案(Dynamic Programming Solution)
动态规划是一种解决具有最优子结构性质问题的有效方法。对于LCS问题,动态规划的基本思想是通过构建一个二维表格来存储子问题的解,然后利用这些子问题的解来构造原问题的解。
步骤:
-
定义状态:
- 设 LCS(𝑖,𝑗)LCS(i,j) 为序列 𝑋[1..𝑖]X[1..i] 和 𝑌[1..𝑗]Y[1..j] 的最长公共子序列的长度。
-
初始化:
- LCS(𝑖,0)=0LCS(i,0)=0 对于所有的 𝑖i,因为任何序列和一个空序列的LCS长度为0。
- LCS(0,𝑗)=0LCS(0,j)=0 对于所有的 𝑗j,因为任何序列和一个空序列的LCS长度为0。
-
状态转移方程:
- 如果 𝑋[𝑖]==𝑌[𝑗]X[i]==Y[j],那么 LCS(𝑖,𝑗)=LCS(𝑖−1,𝑗−1)+1LCS(i,j)=LCS(i−1,j−1)+1。
- 如果 𝑋[𝑖]≠𝑌[𝑗]X[i]=Y[j],那么 LCS(𝑖,𝑗)=max(LCS(𝑖−1,𝑗),LCS(𝑖,𝑗−1))LCS(i,j)=max(LCS(i−1,j),LCS(i,j−1))。
-
构建表格:
- 利用上述状态转移方程,从 LCS(1,1)LCS(1,1) 开始填充整个表格,直到 LCS(𝑚,𝑛)LCS(m,n)(其中 𝑚m 和 𝑛n 分别是序列 𝑋X 和 𝑌Y 的长度)。
-
构建最终解:
- 最终表格的右下角 LCS(𝑚,𝑛)LCS(m,n) 即为两个序列的最长公共子序列的长度。
-
示例:
假设我们有两个序列 𝑋="𝐴𝐵𝐶𝐵𝐷𝐴𝐵"X="ABCBDAB" 和 𝑌="𝐵𝐷𝐶𝐴𝐵𝐴"Y="BDCABA"。
构建二维表格如下:
B D C A B A 0 0 0 0 0 0 0 A 0 0 0 0 1 1 1 B 0 1 1 1 1 2 2 C 0 1 1 2 2 2 2 B 0 1 2 2 2 3 3 D 0 1 2 3 3 3 3 A 0 1 2 3 3 3 4 B 0 1 2 3 3 4 4
最终表格右下角的值4表示最长公共子序列的长度为4。根据表格回溯可以得到最长公共子序列为 "BDAB"。
做题方法:
把两个比较的字符串画在矩阵(二维数组)当中,然后第一行第一列都空着填0;
然后从第二行(第一个字符)开始,如果行列对应字符一样的话,填入左上角数组中加1的数字在该位置,如果不一样的话就选择max(左边格子,上边格子),并都标注好是从哪个数组发展过来的箭头。
Java实现代码
public class LCS {
// 方法:找到两个字符串的最长公共子序列
public static String findLCS(String X, String Y) {
int m = X.length();
int n = Y.length();
// 创建一个二维数组存储子问题的解
int[][] dp = new int[m + 1][n + 1];
// 填充二维数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (X.charAt(i - 1) == Y.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// dp[m][n] 是两个字符串的最长公共子序列的长度
System.out.println("Length of LCS: " + dp[m][n]);
// 从二维数组中回溯以找到最长公共子序列
int i = m, j = n;
StringBuilder lcs = new StringBuilder();
while (i > 0 && j > 0) {
if (X.charAt(i - 1) == Y.charAt(j - 1)) {
lcs.append(X.charAt(i - 1));
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
} else {
j--;
}
}
// 反转字符串以得到正确的顺序
return lcs.reverse().toString();
}
public static void main(String[] args) {
String X = "ABCBDAB";
String Y = "BDCABA";
String lcs = findLCS(X, Y);
System.out.println("Longest Common Subsequence: " + lcs);
}
}
总结
通过动态规划的方法,我们利用最优子结构性质,将一个复杂问题分解为更小的子问题,通过递推的方式逐步求解,最终得到原问题的最优解。这样的方法不仅高效,而且能确保解的正确性。