public class Solution{
/**
* 动态规划
* 状态:dp[i][j] 表示s1前i个字符和s2前j个字符的最长公共子序列的长度
*
* 状态转移方程:
* 若s1[i] = s2[j], dp[i + 1][j + 1] = dp[i][j] + 1
* 为什么不考虑dp[i + 1][j]或dp[i][j + 1]?
* 很简单,不考虑dp[i + 1][j], 是因为s1前i个字符和s2前j - 1个字符的最长公共子序列
* 的长度可能包括了s1[i]。如果包括了,那么它不应该被计算两次,也就是不能
* 再次加入到公共子序列中;如果不包括,dp[i][j]=dp[i + 1][j]是成立的。
* 不考虑dp[i][j + 1]同理。
*
* 若s1[i] != s2[j],dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1])
* 为什么不考虑dp[i][j]?
* 因为s1前i - 1个字符和s2前j - 1的最长公共子序列的长度肯定是小于s1前 i 个字符和
* s2前j - 1个字符以及s1前i - 1个字符和s2前j个字符的最长公共子序列的长度
*
*
* 逆推得到最长公共子序列具体的字符串信息:
* 从 i = s1.len - 1, j = s2.len - 1开始,
* while dp[i + 1][j + 1] > 0:
* 如果s1[i] = s2[j],res += s1[i], i--, j--,两指针同时向前移动
* 否则,
* 如果s1前i个字符和s2前j - 1的最长公共子序列的长度dp[i + 1][j]大于或等于
* s1前i - 1个字符和s2前j的最长公共子序列的长度dp[i][j + 1],j--;
* 反之 i--
* 即逆推的时候总是往最长公共子序列的长度大的方向逆推
*
* longest common subsequence
* @param s1 string字符串 the string
* @param s2 string字符串 the string
* @return string字符串
*/
public String LCS (String s1, String s2) {
// write code here
if(s1 == null || s2 == null) return "-1";
int n = s1.length(), m = s2.length();
if(n == 0 || m == 0) return "-1";
char[] s1Chars = s1.toCharArray();
char[] s2Chars = s2.toCharArray();
int[][] dp = new int[n + 1][m + 1];
// 动态规划求出s1和s2的最长公共子序列的长度
for (int i = 0; i < n; i++){
for (int j = 0; j < m; j++){
if(s1Chars[i] == s2Chars[j])
dp[i + 1][j + 1] = dp[i][j] + 1;
else
dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]);
}
}
StringBuilder sb = new StringBuilder();
// 逆推得到最长公共子序列具体的字符串信息
int i = n - 1, j = m - 1;
while (i >= 0 && j >= 0 && dp[i + 1][j + 1] > 0){
if(s1Chars[i] == s2Chars[j]){
sb.append(s1Chars[i]);
i--;
j--;
} else if(dp[i + 1][j] >= dp[i][j + 1])
j--;
else
i--;
}
return sb.length() > 0 ? sb.reverse().toString() : "-1";
}
}
牛客NC92 输出两个字符串的最长公共子序列(动态规划)
最新推荐文章于 2022-11-09 17:09:19 发布