前言:
lcs问题目前做到了这两道。我学了第一道解题之后,第二道直接ac出来,解法类似,因此此篇主要介绍第一题解题思想。
目录
一、解题思想- 动态规划
1.dp[i][j]的含义
建一个二维表dp[i][j],i 表示第一个字符串的长度范围,j 表示第二个字符串的长度范围。是范围,而不是一定要用i / j字符。相当于是不同范围排列组合列一个表。
而dp[i][j]表示对第一个字符串的范围截取到[0,i] 对于第二个字符串的范围截取到[0,j] 它们的最大公共子序列的长度。
以下第一个字符串统称s1,第二个字符串统称s2。
例如dp[2][3]表示,对于s1,只能用前两个字符构成的子序列,不一定要用上第二个字符,对于s2只能用前三个字符构成子序列。而dp[2][3]表示它们的最大公共子序列的长度。
2.讨论dp[i][j]情况
a:当s1[ i ] == s2[ j ]时
dp[ i ][ j ]就是包括第一个字符串范围可以到i,第二个字符串范围可以到j。
如果s1[i]==s2[j],dp[i-1][j-1]就可以拼上一样的字符构成新的最长子序列。
因此dp[i][j]=dp[i-1][j-1]+1.
b:当s1[ i ] != s2[ j ]时
有3种情况:
dp[ i ][ j ]=dp[ i ][ j-1 ](不要s2的第j个字符,我们剩下的自己玩)
dp[ i ][ j ]=dp[ i-1][ j ](不要s1的第i个字符,我们剩下的自己玩)
dp[ i ][ j ]=dp[ i-1][ j-1 ](即不要s1的第i个字符,s2的第j个字符,我们剩下的自己玩!)
根据dp表的定义,dp[ i ][ j ]=dp[ i-1 ][ j-1 ] 其实被其他两种情况包括了,我们可以去掉这种情况。因此dp[ i ][ j ]等于剩下两种情况的max值。
因此dp[i][j]=max(dp[ i ][ j-1 ],dp[ i-1][ j ])
3.初始化小技巧
对字符串前面加一个空格:
对字符串进行特殊处理 :前面加一个空格
s1=" "+s1; s2=" "+s2;
原因:
在这道题里,空字符串是有研究意义的,当两个当没有公共子序列时,它们的公共子序列就是"",而这个""的长度为0。实际意义上来说,对于后续的dp,也只是等于在前面拼接上一个""(空字符串),不影响解题。
引入这个这个空格是为了简化边界条件的处理。
想象一下,若没加上一个空字符,那么dp[ 0 ][ 0 ]就是s1的第1个字符和s2的第1个字符的范围内的最大子序列。是无法用上面的公式,因为公式涉及[ i -1 ][ j -1],数组越界,那我们就要单独处理这种情况很麻烦。
dp[0][i]
表示s1
的前 0 个字符(即空字符串)与s2
的前i
个字符的最长公共子序列长度。显然,空字符串与任何字符串的最长公共子序列长度都是 0,所以dp[0][i] = 0
。- 同理,
dp[i][0]
表示s1
的前i
个字符与s2
的前 0 个字符(即空字符串)的最长公共子序列长度,也应该是 0。
初始化代码:dp
数组(int[ ][ ])在Java中会自动初始化为 0,因此这部分可省略。
4.填表顺序
公式总结:
a:当s1[ i ] == s2[ j ]时,dp[ i ][ j ]=dp[ i-1 ][ j-1 ]+1.
b:当s1[ i ] != s2[ j ]时,dp[ i ][ j ]=max(dp[ i ][ j-1 ],dp[ i-1][ j ])
根据公式,当我们填dp[ i ][ j ]时,需要如图所示三个量。
因此填表顺序:从左往右,从上往下。
5.返回结果
根据dp的定义可得,返回dp[ s1.length() ][ s2.length() ];
二、 第2题思想-求出最长公共字符串
跟第一题很像!!(看代码就明白了)
细节:注意Java里的String默认为空,所以要把第一行、第一列的初始化为""(空字符串)。
三、代码部分
1.第一题:
//最长公共序列长度
public class test1 {
public static int longestCommonSubsequence(String s1, String s2) {
s1=" "+s1;
s2=" "+s2;
int len1=s1.length();
int len2=s2.length();
int[][] dp=new int[len1][len2];
//初始化空串情况 - 其实可以省略 默认初始化好为0了
for(int i=0;i<len1;i++){
dp[i][0]=0;
}
for(int i=0;i<len2;i++){
dp[0][i]=0;
}
//填表
for(int i=1;i<len1;i++){
for(int j=1;j<len2;j++){
if(s1.charAt(i)==s2.charAt(j)){
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[len1-1][len2-1];
}
}
2.第二题
import java.util.*;
public class Solution {
public String LCS (String s1, String s2) {
//那题第1题一样的思路,只是把个数换成字符串
s1=" "+s1;
s2=" "+s2;
int len1=s1.length();
int len2=s2.length();
String[][] dp=new String[len1][len2];
//初始化""
for(int i=0;i<len1;i++){
dp[i][0]="";
}
for(int i=0;i<len2;i++){
dp[0][i]="";
}
//填充dp表
for(int i=1;i<len1;i++){
for(int j=1;j<len2;j++){
if(s1.charAt(i) == s2.charAt(j)){
dp[i][j]=dp[i-1][j-1]+s1.charAt(i);
}else{
dp[i][j]=dp[i-1][j].length()>dp[i][j-1].length()?dp[i-1][j]:dp[i][j-1];
}
}
}
if(dp[len1-1][len2-1]==null||dp[len1-1][len2-1]==""){
return "-1";
}
return dp[len1-1][len2-1];
}
}