动态规划-子序列

公共子序列问题系列

leetcode 1143 最长公共子序列

题目:
输入两个字符串s1和s2,找出最长公共子序列,返回这个子序列的长度。
函数签名如下:
int longestCommonSubsequence(String s1, String s2);
子序列的问题一般都是两个指针移动求解。如下,我们设置两个指针i与j
在这里插入图片描述
那么假设数组A长度为m,数组B长度为n,那么我们会有dp[i][j],{0<=i<m,0<=j<n 。}表示A[i->m]以及B[j->n]之间的最长子序列的长度。
在这里插入图片描述

那么就是填表环节。我们要填一个mxn的表。我们移动的一个元素是数组的每一个节点,那么我们就使用节点来考虑这个问题。
现在我们考虑节点A [i] 与 B[j]。
(1)如果这两个值是相等的话。
两个节点都相等了。那么肯定在子序列中吧,指针可以都往右边移动了,然后加个1.
dp[i][j] = dp[i+1][j+1] + 1
在这里插入图片描述
(2)如果这俩节点不相等呢?
那就有可能存在一个是最长公共子序列中,又或者两个都不是。(改用excel画图好了,大概就这么个意思)
在这里插入图片描述

那么dp[i]j[]就可能是
dp[i+1][j] 和 dp[i][j+1] 以及dp[i+1][j+1]中最大的那个。

好的,那么我们就可以写代码了。
首先我们填写的表dp是一个mXn的表格,那么我们做出如下的定义
先定义一个dp方法,dp方法的值是memo

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        char[] c1 = text1.toCharArray();
        char[] c2 = text2.toCharArray();
        //定义方法的值memo,也就是备忘录,备忘录初始化值为-1
        int[][] memo = new int[c1.length][c2.length];
        for (int[] row : memo){
            Arrays.fill(row,-1);
        } 
        return dp(c1,0,c2,0,memo);
    }
    public int dp(char[] c1,int i,char[] c2,int j,int[][] memo){
    	//管你三七二十一,dp函数就是要返回memo的值,返回去就对了,沉迷于细节你就死了。
        if(memo[i][j]!=-1){
            return memo[i][j];
        }
        return memo[i][j];
    }
}

把我们上面的推论放上去,其实这里可以不用转换为char数组,字符串完成够用。只是为了思路好理解,放了而已。而且memo其实是可以不用放在dp里面的,只是个人执念而已。。

int[][] memo ; 
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
       memo  = new int[text1.length()][txte2.length()];
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        char[] c1 = text1.toCharArray();
        char[] c2 = text2.toCharArray();
        int[][] memo = new int[c1.length][c2.length];
        for (int[] row : memo){
            Arrays.fill(row,-1);
        } 
        return dp(c1,0,c2,0,memo);
    }
    public int dp(char[] c1,int i,char[] c2,int j,int[][] memo){
        //终止条件为i或者j跑到空的地方
        if(i==c1.length | j==c2.length){
            return 0;
        }
        if(memo[i][j]!=-1){
            return memo[i][j];
        }
        //dp的判断
        if(c1[i] == c2[j]){
            memo[i][j] = dp(c1,i+1,c2,j+1,memo)+1;
        }else{
            memo[i][j] = Math.max(dp(c1,i+1,c2,j,memo) , dp(c1,i,c2,j+1,memo));
            memo[i][j] = Math.max(memo[i][j] , dp(c1,i+1,c2,j+1,memo));
        }
        return memo[i][j];
    }

}

leetcode712 最小 ASCII 删除和

在这里插入图片描述
这道题很明显还是求两个字符串最长的公共子序列。
这时候我们的dp函数变了。dp[m][n] = 最小的ascii。
不管三七二十一,先写上去。dp的值=memo,那么,定义memo和dp函数,在dp函数中返回memo

class Solution {
    public int minimumDeleteSum(String s1, String s2) {
    //定义memo
        char[] c1 = s1.toCharArray();
        char[] c2 = s2.toCharArray();
        int[][] memo = new int[c1.length][c2.length];
        for (int[] row : memo){
            Arrays.fill(row,-1);
        } 
        return dp(c1,0,c2,0,memo);
    }
    public int dp(char[] c1,int i,char[] c2,int j,int[][] memo){
        //管你三七二十一返回memo
        if(memo[i][j]!=-1){
            return memo[i][j];
        }
    }
}

然后思考dp函数
这时候我们的每个单元是char[i] 和char[j]来比较
(1)相等的时候
很好,这时候不用删除,那么dp[i][j] = dp[i+1][j+1]
(2)不相等的时候
dp[i][j] = {

a.删除c1[i]的时候 c1[i]+dp[i+1][j] 这时候c2[j]
b.删除c2[j]的时候 c2[j] + dp[i][j+1]
c.都删的时候,dp[i+1][j+1]+c1[i]+c2[j]
在这里插入图片描述
那么base case确定一下
当i = c1.length的时候,也就是A是空串,那么B都得删光
在这里插入图片描述
同理B是空串也就是j=c2.lenngth的时候,A都得删光。那么代码如下

class Solution {
    public int minimumDeleteSum(String s1, String s2) {
        char[] c1 = s1.toCharArray();
        char[] c2 = s2.toCharArray();
        int[][] memo = new int[c1.length][c2.length];
        for (int[] row : memo){
            Arrays.fill(row,-1);
        } 
        return dp(c1,0,c2,0,memo);
    }
    public int dp(char[] c1,int i,char[] c2,int j,int[][] memo){
        //终止条件为i或者j跑到空的地方,另一个字符串的都要删掉
        int res = 0;
        if(i==c1.length){ //删光c2
            for(int k=j;k<c2.length;k++){
                res += c2[k];
            }
            return res;
        }
        if(j==c2.length){ //删光c1
             for(int k=i;k<c1.length;k++){
                res += c1[k];
            }
            return res;
        }
        if(memo[i][j]!=-1){
            return memo[i][j];
        }
        //dp的判断
        if(c1[i] == c2[j]){
            //相等不用删除
            memo[i][j] = dp(c1,i+1,c2,j+1,memo);
        }else{
            //这里要求最小值,不相等的时候加上删除的部分
            memo[i][j] = Math.min(dp(c1,i+1,c2,j,memo)+c1[i] , dp(c1,i,c2,j+1,memo)+c2[j]);
            memo[i][j] = Math.min(memo[i][j] , dp(c1,i+1,c2,j+1,memo)+c1[i]+c2[j]);
        }
        return memo[i][j];
    }
}

leetcode 583 两个字符串的删除操作

题目:
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
int minDistance(String s1, String s2);
解法:
那么说白了就还是求公共子序列。那么我们使用word1.length() + word2.length() - 2*ength(公共子序列)即可。那么复用一下leetcode 1143的代码

class Solution {
    public int minDistance(String word1, String word2) {
        char[] c1 = word1.toCharArray();
        char[] c2 = word2.toCharArray();
        int[][] memo = new int[c1.length][c2.length];
        for (int[] row : memo){
            Arrays.fill(row,-1);
        } 
        //-----其实就改了这行
        return word1.length() + word2.length() - dp(c1,0,c2,0,memo)*2;
    }
     public int dp(char[] c1,int i,char[] c2,int j,int[][] memo){
        if(i==c1.length | j==c2.length){
            return 0;
        }
        if(memo[i][j]!=-1){
            return memo[i][j];
        }
   
        if(c1[i] == c2[j]){
            memo[i][j] = dp(c1,i+1,c2,j+1,memo)+1;
        }else{
            memo[i][j] = Math.max(dp(c1,i+1,c2,j,memo) , dp(c1,i,c2,j+1,memo));
            memo[i][j] = Math.max(memo[i][j] , dp(c1,i+1,c2,j+1,memo));
        }
        return memo[i][j];
    }

}

leetcode 300 最长递增子序列

题目:
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
解法:
首先不管三七二十一,要填表吧?这一个数组那么一个就填一行,那么就是一个一维数组。
定义dp[i] = 以i开头的数组nums最长递增子序列的长度
然后dp函数的值存到memo中。
那么

class Solution {
    int[] memo;
    public int lengthOfLIS(int[] nums) {
       //定义memo
        memo = new int[nums.length];
        Arrays.fill(memo,-1);
        return dp(nums,0);
    }
    //dp函数返回memo
    public int dp(int[] nums,int i){
        if(memo!=-1){
            return memo[i];
        }
        return memo[i];
    }
}

定义完之后dp[i]要开始设计了,粒度是啥?数组的一个节点。
我是nums[i],现在轮询到我这儿了,我该做点啥呢?由于我们的dp定义是从i开始,那么我们和后面的比较
我先比较我面那个数字nums[i+1]呗,如果我比它大,那么dp[i+1]=dp[i]+1,不然的话dp[i] = dp[i+1]。。等会怎么感觉好像不对。。?因为被比较的两个值并不一定会在子序列中!
那么我们dp[i]重新定义一下(==),以nums[i]为结尾的最长递增子序列。这样我们可以比较了.上面那个函数重新定义一下。。

class Solution {
    int[] memo;
    int max = Integer.MIN_VALUE;//求最大,放最小。
    public int lengthOfLIS(int[] nums) {
        memo = new int[nums.length];
        Arrays.fill(memo,1);
        dp(nums,nums.length-1);
        for(int num:memo){
            System.out.print(" "+num);//   错误:1 1 1 2 2 3 1 4
        }                              //  1 1 1 2 2 3 4 4
        for(int n:memo){
            max = max>n?max:n;
        }
        return max;
    }
    public int dp(int[] nums,int i){
        if(i==0){
            memo[i]=1; 
        }
        for(int j=0;j<i;j++){
            if(nums[i]>nums[j]){
                memo[i] = Math.max(dp(nums,j) + 1,memo[i]);
            }
        }
        return memo[i];
    }
}
===============
输入
[1,3,6,7,9,4,10,5,6]
输出
5
预期结果
6
stdout
 1 2 1 1 1 3 1 4 5
==============

class Solution {
    int[] memo;
    int max = Integer.MIN_VALUE;//求最大,放最小。
    public int lengthOfLIS(int[] nums) {
        memo = new int[nums.length];
        Arrays.fill(memo,1);
        dp(nums,nums.length-1);
        for(int num:memo){
            System.out.print(" "+num);//   错误:1 1 1 2 2 3 1 4
        }                              //  正确:1 1 1 2 2 3 4 4
        for(int n:memo){
            max = max>n?max:n;
        }
        return max;
    }
    public int dp(int[] nums,int i){
        if(i==0){
            memo[i]=1; 
        }
        for(int j=0;j<i;j++){
            if(nums[i]>nums[j]){
                memo[i] = Math.max(dp(nums,j) + 1,memo[i]);
            }
        }
        return memo[i];
    }
}
=================
输入
[1,3,6,7,9,4,10,5,6]
输出
6
预期结果
6
stdout
 1 2 3 4 5 3 6 4 5
=================


============
终于正确的一个示范
============
class Solution {
    int[] memo;
    int max = Integer.MIN_VALUE;//求最大,放最小。
    public int lengthOfLIS(int[] nums) {
        memo = new int[nums.length];
        Arrays.fill(memo,-1);
        for(int i=0;i<nums.length;i++){
            dp(nums,i);
        }
        for(int num:memo){
            System.out.print(" "+num);
        }                             
        for(int n:memo){
            max = max>n?max:n;
        }
        return max;
    }
    public int dp(int[] nums,int i){
        if(i==0){
            memo[i] = 1;
        }
        if(memo[i]!=-1){
            return memo[i];
        }
        for(int j=0;j<i;j++){
            if(nums[i]>nums[j]){
                memo[i] = Math.max(dp(nums,j) + 1,memo[i]);
            }
        }
        if(memo[i] ==-1){
            memo[i] =1;
        }
        return memo[i];
    }
}

好的,粒度不变我们还是一个数组的一个元素,我要干什么?我就和前面那个比较吧
(1)如果我大于nums[i-1]
dp[i] = dp[i-1] +1 我比我前面那个大嘛,那么以它结尾的递增子序列可以再加上我。
(2)不然的话
dp[i] = 1 以我结尾的就我自己好了
那么最长的应该是把dp数组遍历一遍,取个最大值 .这样有没有问题?有。
比如说

在这里插入图片描述

4肯定比5小,可是4可以和1在一块。谁告诉你dp[3] =1?。正确的思路把前面的数组中的比nums[i]的下标的每个dp都遍历一遍,咱门接上去取最大值就完事儿了

以下错误示范。
class Solution {
    int[] memo;
    int max = Integer.MIN_VALUE;//求最大,放最小。
    public int lengthOfLIS(int[] nums) {
        memo = new int[nums.length];
        Arrays.fill(memo,1);
        dp(nums,nums.length-1);
        for(int num:memo){
            System.out.print(" "+num);//   错误:1 1 1 2 2 3 1 4
        }                              //  正确:1 1 1 2 2 3 4 4
        for(int n:memo){
            max = max>n?max:n;
        }
        return max;
    }
    public int dp(int[] nums,int i){
        if(i==0){
            memo[i]=1; 
        }
        for(int j=0;j<i;j++){
            if(nums[i]>nums[j]){
                memo[i] = Math.max(dp(nums,j) + 1,memo[i]);
            }
        }
        return memo[i];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值