题目描述
https://leetcode-cn.com/problems/longest-common-subsequence/
解法
(1)自顶向下递归(动态规划)
class Solution {
// 备忘录,消除重叠子问题
int[][] memo;
/* 主函数 */
int longestCommonSubsequence(String s1, String s2) {
int m = s1.length(), n = s2.length();
// 备忘录值为 -1 代表未曾计算
memo = new int[m][n];
for (int[] row : memo)
Arrays.fill(row, -1);
// 计算 s1[0..] 和 s2[0..] 的 lcs 长度
return dp(s1, 0, s2, 0);
}
// 递归函数,自顶向下的递推,其问题的定义:计算 s1[i..] 和 s2[j..] 的最长公共子序列长度
int dp(String s1, int i, String s2, int j) {
if (i == s1.length() || j == s2.length()) {
return 0;
}
// 如果之前计算过,则直接返回备忘录中的答案
if (memo[i][j] != -1) {
return memo[i][j];
}
// 根据 s1[i] 和 s2[j] 的情况做选择
if (s1.charAt(i) == s2.charAt(j)) {
// s1[i] 和 s2[j] 必然在 lcs 中
memo[i][j] = 1 + dp(s1, i + 1, s2, j + 1);
} else {
// s1[i] 和 s2[j] 至少有一个不在 lcs 中
memo[i][j] = Math.max(
dp(s1, i + 1, s2, j),
dp(s1, i, s2, j + 1)
);
}
return memo[i][j];
}
}
(2)自底向上的动态规划
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
if(text1==null || text2==null) return 0;
int m = text1.length(),n=text2.length();
int [][]dp = new int[m+1][n+1];
//边界
//dp[0][..]表示s1[-1]和s2[...]的最长公共子序
//dp[..][0]表示s2[-1]和s1[...]的最长公共子序
//均为0
//状态递推--严格遵循定义的dp数组 dp[i][j]表示是s1[0,i-1]和s1[0,j-1]的最长公共子序
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(text1.charAt(i-1)==text2.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]);
}
}
}
return dp[m][n];
}
}
经验:
- 严格按照自己定义的dp数组的含义来推递推方程
- 以一个简短实际例子来递推状态方程
- 自顶向下和自底向上的动态规划dp数组的含义方向相反。
- 字符串的dp问题一般使用的都是双指针,然后要不就是以i,j结尾的子问题的解要不就是以i,j开头的子问题的解。