动态规划的总结

动态规划

背包类的问题

组合类

示例 1:

输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

    public int combinationSum4(int[] nums, int target) {
        int n=nums.length;
        int []dp=new int[target+1]; //背包0算一个
        dp[0]=1;
        for (int i = 0; i <= target; i++) {
            for (int j = 0; j < n; j++) {
                if(i>=nums[j]){
                    dp[i]+=dp[i-nums[j]];
                }
            }
        }
        return dp[target];
    }
我认为这里需要注意的不是什么遍历的顺序问题,不应该这么刻意去记
只需要记得在不考虑顺序的时候(组合)背包的循环应该在里面,这使得每一个货物只能遍历一次
而在考虑顺序的排列问题的时候,背包的遍历顺序应该在外面,然后货物的遍历在里面,
这是因为货物在里面在不同的背包大小的时候可以重复计算。

排列类问题

不区分

  1. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

示例 4:

输入:coins = [1], amount = 1
输出:1

示例 5:

输入:coins = [1], amount = 2
输出:2

提示:

1 <= coins.length <= 12
1 <= coins[i] <= 2^31 - 1
0 <= amount <= 10^4
在这里插入代码片
    public int coinChange(int[] coins, int amount) {
        int max=Integer.MAX_VALUE;
        int []dp=new int[amount+1];
        for (int i = 0; i < amount+1; i++) {
            dp[i]=max;
        }
        dp[0]=0;  //因为始终上来说的话amount为0表示的是不用取,即方法为0
        for (int i = 0; i < coins.length; i++) {  //表示选取第几个物品
            for (int j = coins[i]; j <= amount; j++) {
                if(dp[j-coins[i]]!=max){
                    dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
                }
            }
        }
        return dp[amount]==max ? -1:dp[amount];
    }
在这里插入代码片
思路:我想说的是其实重点在于调试的过程,以及dp[i]的含义,首先dp[j]:
凑足总额为j所需钱币的最少个数为dp[j],然后注意的是在选定第i件物品的时候,
是从coin[i]开始的,然后以及选定了coin[i]的物品进背包后,就相当于从dp[j-coin[i]]
开始放置物品的,那么此时的话,需要注意的是当dp[j-coins[i]]!=max是没有意义的,
因为之前的容量下无法放下东西,最后得到的递归方程式是
:dp[j]=Math.min(dp[j],dp[j-coins[i]]+1)
在这里我意识到了什么时候应该取矩阵的维度是n+1,就是说,当你的背包数为0占一个位置的时候需要取n+1

股票类问题

编辑距离类问题

值得注意的是在编辑距离相关的问题中,其实dp[][]的下标是可以从i,j开始的,只不过不是很方便,因为如果dp[i][j]代表的是下标为i和j的字符串之间的距离的话,会导致dp[0][0] ,以及相应的dp[i][0]需要在初始化的时候增加一个判断,因为如果s.chatAt(i)==t.charAt(0)的时候这意味着dp[i][0]的值为1,而如果以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j],就不需要在初始化的时候考虑这个问题,因为此时的话,dp[i][1]才是表示的i-1与0的距离,可以从dp[i-1][0]开始进行递推。

在这里插入代码片
这种类型的题目的话,一般都是当两者相等的时候一个关系,然后就是else的时候又是一个关系,递推就完事了,但这里
最主要的就是理解dp数组的含义是什么,dp[i][j]通常表示的是字符串之间的转换关系,可以是匹配关系什么的,有多少
个子序列这种,然后可以通过递推进行。初始化的条件通常是重要的,这与实际的情况有关,例如不同的子序列这道题。

115.不同的子序列
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

题目数据保证答案符合 32 位带符号整数范围。
在这里插入图片描述

在这里插入代码片
    public int numDistinct(String s, String t) {
        int m=s.length();
        int n=t.length();
        int [][]dp=new int[m+1][n+1];
        for (int i = 0; i <=m; i++) {
            dp[i][0]=1; //可以通过删除s的值达到t
        }
        for (int i = 1; i <=m ; i++) {
            for (int j = 1; j <= n; j++) {
                if(s.charAt(i-1)==t.charAt(j-1)){
                    dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
                }else{
                    dp[i][j]=dp[i-1][j];//只能通过前面的s来进行匹配了
                }
            }
        }
        return dp[m][n];
    }

在这里插入代码片
思路:dp[i][j]的含义是有s转成t的方法的个数,则初始化的时候,要先
        for (int i = 0; i <=m; i++) {
            dp[i][0]=1; //可以通过删除s的值达到t
        }
这是因为可以对s进行删除得到t;
s.charAt(i-1)==t.charAt(j-1)的时候,可以得到dp[i][j]
其值等于dp[i-1][j]+dp[i-1][j-1],因为第一个dp[i-1][j]代表的是把当前的s[i-1]
删去,看看之前是否可以匹配t[j-1]的,dp[i-1][j-1],还有就是不考虑当前的即
不需要考虑当前s子串和t子串的最后一位字母的情况,因为当前这一位一定可以匹配了。

最长连续公共子序列(利扣自己的改进版)

最长连续公共子序列(Longest Common Substring)问题是指在两个字符串中找到一个最长的子串,使得这个子串在两个字符串中都出现过,并且连续出现。

以下是求解最长连续公共子序列的一种动态规划算法:

假设给定两个字符串 A 和 B,它们的长度分别为 m 和 n。我们可以定义一个二维数组 dp,其中 dp[i][j] 表示以 A[i-1] 和 B[j-1] 结尾的最长连续公共子序列的长度。这里的 A[i-1] 和 B[j-1] 表示字符串 A 和 B 中从 0 开始的第 i-1 和 j-1 个字符。

动态规划的递推式如下:

当 i=0 或 j=0 时,dp[i][j] = 0。
当 A[i-1] = B[j-1] 时,dp[i][j] = dp[i-1][j-1] + 1。
当 A[i-1] ≠ B[j-1] 时,dp[i][j] = 0。
最长连续公共子序列的长度就是数组 dp 中的最大值,最长连续公共子序列本身可以通过回溯数组 dp 来构造。

在这里插入代码片
    public static int longestCommonSubstring(String A, String B) {
        int m = A.length();
        int n = B.length();
        int[][] dp = new int[m+1][n+1];
        int maxLength = 0;

        // 动态规划求解最长连续公共子序列
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (A.charAt(i-1) == B.charAt(j-1)) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                    maxLength = Math.max(maxLength, dp[i][j]);
                } else {
                    dp[i][j] = 0;
                }
            }
        }

        return maxLength;
//        return dp[m][n];
    }

最短公共超序列(每日一题)

给出两个字符串 str1 和 str2,返回同时以 str1 和 str2 作为子序列的最短字符串。如果答案不止一个,则可以返回满足条件的任意一个答案。
输入:str1 = “abac”, str2 = “cab”
输出:“cabac”
解释:
str1 = “abac” 是 “cabac” 的一个子串,因为我们可以删去 “cabac” 的第一个 "c"得到 “abac”。
str2 = “cab” 是 “cabac” 的一个子串,因为我们可以删去 “cabac” 末尾的 “ac” 得到 “cab”。
最终我们给出的答案是满足上述属性的最短字符串。

在这里插入代码片
思路:相当于先计算最长的公共子串,dp[i][j]表示的是最长的公共子串的长度,计算应该很简单,然后再进行判断dp[i][j]与dp[i-1][j],dp[i][j-1],dp[i-1][j-1]之间的关系,其中dp[i-1][j]表示的是将i-2为下标的情况下和i-1为下标的情况一致,这种情况的话表明就是i-1是没有出现过的,应该加上它;dp[i][j-1]的原理一致。
在这里插入代码片
public class SuperSequece {
    public String shortestCommonSupersequence(String str1, String str2) {
        int m=str1.length();
        int n=str2.length();
        int [][]dp=new int[m+1][n+1];
        for (int i = 1; i <=m ; i++) {
            for (int j = 1; j <=n ; j++) {
                if(str1.charAt(i-1)==str2.charAt(j-1)){ //最长公共子序列
                    dp[i][j]=dp[i-1][j-1]+1;
                }else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        int i=m;
        int j=n;
        StringBuffer sb=new StringBuffer();
        while (i>0 || j>0){
            if(i==0){
                j--;
                sb.append(str2.charAt(j));
            } else if (j==0) {
                i--;
                sb.append(str1.charAt(i));
            } else {
                if (dp[i][j]==dp[i-1][j]) {  //意味着i-1下标没有出现过
                    i--;
                    sb.append(str1.charAt(i));
                }else if(dp[i][j]==dp[i][j-1]){
                    j--;
                    sb.append(str2.charAt(j));
                }else {
                    i--;
                    j--;
                    sb.append(str1.charAt(i));
                }
            }
        }
        return sb.reverse().toString();
    }

    public static void main(String[] args) {
        String str1="abac";
        String str2="cab";
        SuperSequece superSequece=new SuperSequece();
        System.out.println(superSequece.shortestCommonSupersequence(str1,str2));

    }
}

序列DP类题目

一开始使用复杂度为n3的动态规划导致超时了,后来以空间换时间,将每一个数据和索引存在hashmap中,使得在寻找符合要求的l的时候就很方便了,直接在hashmap中查找,找到的话直接返回索引,找不到就是-1

 dp[j][l]=dp[l][i]+1;
 且l在i和j之间
class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int n=arr.length;
        int [][]dp=new int[n][n];
        int max=0;
        HashMap<Integer,Integer> hashMap=new HashMap<>();
        for (int i = 0; i < n; i++) {
            hashMap.put(arr[i],i);
        }
        for (int i = 0; i < n; i++) {
            for (int j = i+2; j <n ; j++) {
                    int l=hashMap.getOrDefault(arr[j]-arr[i],-1);
                    if(i<l && l<j){
                        dp[j][l]=dp[l][i]+1;
                        max=Math.max(max,dp[j][l]);
                    }
                    
            }
        }
        return max==0 ? 0:max+2;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值