每天一道动态规划——第五天
上左程云大神的课学到了第五题的时候,大神认为动态规划目前可以分为3类(当然后续还会有更复杂的动态规划模型)
1.从左到右模型
2.范围模型
3.样本模型
从左到右模型就是我的文章《每天一道动态规划——第二天》里说的玩家分别拿牌的问题。玩家一般是从左到右(按顺序)有不同的选择。
今天我们将要学到的样本模型是一种需要考虑结尾样本所有可能选择的模型。
最最最重要的还是要强调自然智慧。同时我觉得最好从边界开始尝试,因为一般边界涉及的东西比较少,人不至于麻了哈哈。
1)题目
假设有两个字符串
str1=“a1b2c3”
str3=“abc124”
问这两个字符串的最长公共子序列的长度
答案是:4
解释:“abc3”是最长的公共子序列(这里没有说连续的子序列哦)
在Leetcode中是这个题目:
LCR 095. 最长公共子序列
2)代码与思路
我们这样考虑,如果我们只考虑一段字符串这个问题会不会简单点
比如:
str1[0,1,…,i]
str2[0,1,…,j]
(PS:i和j不一定相等哦)
可能str1的长度不止i,str2的长度不只j,那我不管,我就先考虑这一段。
一般来说我们写代码会先写边界条件
1.如果两个字符串其中一个为null或者是空字符串那就直接返回为0,因为没有匹配啊
if(str1==null||str2==null||str1.length()==0;str2.length()==0){
return 0;
}
2.那如果两个字符串都只剩一个字符了,那就匹配返回1,不匹配返回0.
if(i==0,j==0){
return str1[i]==str2[j]?1:0;
}
3.第三种情况是只有一个字符串剩余一个字符,比如str1。那就让这一个字符和另一个字符串的最后一个进行匹配。匹配的上,最多长度也就是1,匹配不上那确实也是没有办法(bushi),往前再看看呢?就考虑str2[0:j-1]重复这个process过程。
同样如果str2只剩下一个字符也是一样的。
if(i==0){
return str1[i]==str2[j]?1;process(str1,str2,i,j-1);
}
if(j==0){
return str1[i]==str2[j]?1:process(str1,str2,i-1,j);
}
4.最后一个就是普遍情况了,两个字符串都还有挺多的。
根据上面的思路的提示,一般我们要判断str1和str2最后一个字符串是不是一致呢?如果一致就非得用嘛?所以就有一些可能。
最后一个字符串匹配:process(str1,str2,i-1,j-1)+1;
最后一个字符串匹配和不匹配都可以搞的:
process(str1,str,i-1,j)
process(str1,str,i,j-1)
最后一个样本的所有下一步的可能就是这样子,因此我么可以写出逻辑代码:
int p1=process(str1,str2,i-1,j);
int p2=process(str1,str2,i,j-1);
int p3=0;
if(str1[i]==str2[j]){
p3=process(str1,str2,i-1,j-1);
}
return Math.max(Math.max(p1,p2),p3);
所有的思路就是这样了,那我们的总体的代码就是
public class solution5 {
static String str1="a1b2c3";
static String str2="abc123";
public static void main(String[] args) {
ways1();
}
public static void ways1(){
char[] chars1 = str1.toCharArray();
char[] chars2 = str2.toCharArray();
int result = process1(chars1, chars2,chars1.length - 1,chars2.length - 1);
System.out.println(result);
}
public static int process1(char[] str1,char[] str2,int i,int j){
if(str1==null||str2==null||str1.length==0||str2.length==0){
return 0;
}
if(i==0&&j==0){
return str1[i]==str2[j]?1:0;
}
if(i==0){
return str1[i]==str2[j]?1:process1(str1,str2,i,j-1);
}
if(j==0){
return str1[i]==str2[j]?1:process1(str1,str2,i-1,j);
}
int p1=process1(str1,str2,i-1,j);
int p2=process1(str1,str2,i,j-1);
int p3=0;
if(str1[i]==str2[j]){
p3=process1(str1,str2,i-1,j-1)+1;
}
return Math.max(Math.max(p1,p2),p3);
}
}
3)代码优化
说道优化,我们第一个想到的肯定是傻缓存法。这个缓存法的前提是有重复解出现。试了一下是有重复解出现的,所以可以用这个方法。
构建一个dp[i][j]
已经初始化就从中取出值没有初始化就继续按照原来的那一套走。
下面是方法,在主函数里面调用ways2()就可以了。
public static void ways2(){
char[] chars1 = str1.toCharArray();
char[] chars2 = str2.toCharArray();
int[][] dp=new int[chars1.length][chars2.length];
for(int i=0;i<chars1.length;i++){
for(int j=0;j<chars2.length;j++){
dp[i][j]=-1;
}
}
int result = process2(chars1, chars2, chars1.length-1, chars2.length-1, dp);
System.out.println(result);
}
public static int process2(char[] str1,char[] str2,int i,int j,int[][] dp){
if(dp[i][j]!=-1){
return dp[i][j];
}
if(str1==null||str2==null||str1.length==0||str2.length==0){
return 0;
}
if(i==0&&j==0){
return str1[i]==str2[j]?1:0;
}
if(i==0){
return str1[i]==str2[j]?1:process2(str1,str2,i,j-1,dp);
}
if(j==0){
return str1[i]==str2[j]?1:process2(str1,str2,i-1,j,dp);
}
int p1=process2(str1,str2,i-1,j,dp);
int p2=process2(str1,str2,i,j-1,dp);
int p3=0;
if(str1[i]==str2[j]){
p3=process2(str1,str2,i-1,j-1,dp)+1;
}
dp[i][j]=Math.max(Math.max(p1,p2),p3);
return dp[i][j];
}
4)进阶优化
缓存的下一个优化就是给出表的初始条件,按初始条件填表。
初始条件不就是边界条件那些嘛。能给出第一行和第一列的值
然后下面的一般情况就是数组的的填写逻辑。
代码就是下面这个:
public static void ways3(){
char[] chars1 = str1.toCharArray();
char[] chars2 = str2.toCharArray();
int[][] dp=new int[chars1.length][chars2.length];
dp[0][0]=chars1[0]==chars2[0]?1:0;
//初始化第一行
for(int j=1;j< chars2.length;j++){
dp[0][j]=chars1[0]==chars2[j]?1:dp[0][j-1];
}
//初始化第一列
for(int i=1;i<chars1.length;i++){
dp[i][0]=chars1[i]==chars2[0]?1:dp[i-1][0];
}
//填剩余的表格
for(int i=1;i<chars1.length;i++){
for(int j=1;j<chars2.length;j++){
int p1=dp[i-1][j];
int p2=dp[i][j-1];
int p3=0;
if(chars1[i]==chars2[j]){
p3=dp[i-1][j-1]+1;
}
dp[i][j]=Math.max(Math.max(p1,p2),p3);
}
}
System.out.println(dp[chars1.length-1][chars2.length-1]);
}
好啦,今天的动态规划就到这里了,我们去leetcode刷一道