子序列与字串的区别:
子序列不要求公共部分在原字符串中连续,而字串要求连续。
动态规划求解:
例子:"bdcaba", "abcbdab" 最长的公共子序列为 b c b a 和 b d a b
1:首先分析状态转移数组dp[i][j]的含义:
指的是 字符串 s1 从 0到n-1 和 字符串 s2 从 0到m-1 的最大公共子序列的长度。
如上面 dp[0][0]=0 因为 s1的“b” 和s2的'a"不相等,所以最大公共子序列的长度为0
dp[0][1]=1 因为 s1的“b” 和s2的'ab"有一个字母相等,所以最大公共子序列的长度为1
以此类推以此计算 到 dp[0][m-1],再计算dp[1][0]到dp[1][m-1].......到dp[n-1][m-1]。
2:计算过程
(1)计算dp[i][j]时,先比较s1.charAt(i)是否等于s2.charAt(j),如果等于,就把dp[i][j]的长度置为dp[i-1][j-1]+1,为什么是dp[i-1][j-1]+1呢?
因为当你的当前位置(i ,j)的对应字母相等时,最大长度就是上一次的最大长度加1,而上一次的最大长度就是dp[i-1][j-1]。比如:
当比较到 dp[2][2]时,即 字符串 s1 “bdc”和 字符串 s2 "abc" 的最大公共子序列的长度。因为s1.charAt(i)等于s2.charAt(j),所以最大长度要在上一次的最大长度上加一,而上一次指的就是“bd”和"ab"也就是i-1和j-1。
(2)如果不相等,那就要判断是少一个i的长度大些呢,还是少一个j的长度大些呢,也就是判断dp[i-1][j]和dp[i][j-1]哪个大,找出大的放进dp[i][j]。
(3)最大长度肯定是dp[n-1][m-1]。因为这是完整的两个串的最大子序列长度。
3:输出
(1)因为dp数组最后一个是最大的长度,所以就从最后一个开始回溯。
(2)比较s1.charAt(i)是否等于s2.charAt(j),如果等于就是输出该字母。
(3)不等于就判断dp[i-1][j]和dp[i][j-1]哪个大,如果dp[i-1][j]大,就到他的位置,也就是j不变,i-1;否则就相反。
(4)重复(2)(3)直到i<=1&&j<=1
4: 上面的思路用图表达就是把s1和s2弄成一个二维表
5:代码
public int[][] LCS(String s1,String s2){
int dp[][] = new int[s1.length()+1][s2.length()+1];
for (int i = 0; i <= s1.length(); i++) {
dp[i][0] = 0;
}
for (int i = 0; i <= s2.length(); i++) {
dp[0][i] = 0;
}
for (int i = 1; i <= s1.length(); i++) {
for (int j = 1; j <= s2.length(); j++) {
if(s1.charAt(i-1) == s2.charAt(j-1)){ //如果相等
dp[i][j] = dp[i-1][j-1]+1;
}else{
if(dp[i-1][j]>dp[i][j-1]){
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=dp[i][j-1];
}
}
}
}
System.out.println("最长最序列长度:"+dp[s1.length()][s2.length()]);
for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[0].length; j++) {
System.out.print(dp[i][j]+" ");
}
System.out.println();
}
System.out.println("-------输出-------------");
int tempS1 = s1.length();
int tempS2 = s2.length();
while(tempS1>=1 && tempS2>=1){
if(s1.charAt(tempS1-1)==s2.charAt(tempS2-1)){
System.out.print(s2.charAt(tempS2-1)+" ");
tempS1--;
tempS2--;
}else{
if(dp[tempS1-1][tempS2]>dp[tempS1][tempS2-1]){
tempS1--;
}else{
tempS2--;
}
}
}
return dp;
}