目录-动态规划问题整理

介绍

本篇文章为大熊个人的动规笔记,非算法教程。

技巧性总结

从台阶看分析动规时候的误区

要注意分清楚
有时候会傻逼的认为
那么取决于n-1,是不是f(n) = f(n-1) +1 …但是这是方法数,不是路径长度,事实上如果只考虑前进一步的话,f(n)与f(n-1)是同一条的路径达到的,所以其实是同一个方法。我们看,这其实是一颗多叉决策树
在这里插入图片描述

也就是说,我们事实上求得是这颗树的一个分支条数(方法),而非到达该分支的时候的步数(路程),比如达到2的方法树,其实我们看的只有条数,而不是节点数
在这里插入图片描述
很明显,上面只有两条。那么下面的1-2-3台阶其实也是一样的道理。如果要是求路径数,也就是节点长度,那么肯定会有个最值

初始化的时候该初始化到多少?

根据dp公式

比如dp[i] = dp[i-3] + dp[i-2]+dp[i-1] i减去的那个最大有3,那么应该初始化到3才能保证dp[i-3]取到i0

一般的矩形

一般二维数组需要初始化可以做出选择的两列,详见矩阵路径。
在这里插入图片描述

打印dp数组

刷题总结时候一定要记得打印dp数组,不然写了也是白写。比如刷leetcode的时候像这样打印出来

在这里插入图片描述

题目类型

台阶与矩阵

大熊的动态规划-台阶与矩阵笔记

背包

背包就是:能背负的重量为Wb
物品也有自己的重量Wg ,以及自己的价值Vg,然后用背包装东西求价值最大

牛客网 0-1 背包问题

题目:
在这里插入图片描述
借用一下算法大神labuladong的框架

for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 择优(选择1,选择2...)

我自行修改一下以上框架

for X1 in X1的所有取值:
    for X2 in X2的所有取值:
        for ...
            dp[X][X2][...] = 择优(选择1,选择2...)

X1、X2…Xn均为变量,dp[X1][X2]的值为y
那么我们看看01背包中的x和y都有哪些
首先有一下的变量
y我们先确定,y就是价值,我们要求的就是价值
那么x可以从如下几个地方取

a.物品两个属性,物品单个价值不算,因为y就是总价值,同个维度,所以应该取物品的个数。
b.背包的重量范围 ,这个肯定是x
那么就是y=dp[a][b],当背包重量为b,物品个数为a的时候,y就是这个时候背包物品的最大价值。

那么接下来我们的代码将会是这样的

int dp[a][b]
for(int[] row:dp){
	Arrays.fill(row,-1)
}
for i in [1..a]:
   for j in [1..b]:
         //判断;如果物品放不进去背包
         //物品可以放进背包
        dp[i][j] = max(把物品装入背包,不把物品加入背包)
return dp[i][j]

问题来了,什么他么的叫把物品装入背包?做选择的时候一定要牢记dp的定义!
dp是什么,重量为a,个数为b的时候的最大值,什么的最大值?价值!
不装把
好,dp[i][j] = dp[i]-1[j] 反正重量j一定的情况下要不要这个东西都一样
装把
好 dp[i][j] = dp[i-1[j-物品重量]+物品价值。这时候i和j都在变。
这时候我们处理一下代码,感觉牛客网的判定有点问题,不管了。

public class Main {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 计算01背包问题的结果
     * @param V int整型 背包的体积
     * @param n int整型 物品的个数
     * @param vw int整型二维数组 第一维度为n,第二维度为2的二维数组,vw[i][0],vw[i][1]分别描述i+1个物品的vi,wi
     * @return int整型
     */
    
    public int knapsack (int V, int n, int[][] vw) {
        // write code here
        if(V==0 || n==0 || vw==null){
            return 0;
        }
        int[][] dp=new int[n+1][V+1];
        for(int i=1;i<=n;i++){
            for(int j=1;j<=V;j++){
                if(j<vw[i-1][0]){
                    dp[i][j]=dp[i-1][j];
                }
                else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-vw[i-1][0]]+vw[i-1][1]);
                }
            }
        }
        return dp[n][V];
    }
}

494. 目标和

leetcode416 分割等和子集

在这里插入图片描述
这个问题也是一个背包的问题。
分割两个等和子集也就是把数字塞进容量为sum/2为容量的背包。
那么设置dp[n][m],物品个数是n,背包容量是m
y=dp[n][m],代表着前n个物品,能不能正好装满容量为m的背包
那么怎么考虑base case呢?
当n为0,m不是0的时候,没有东西,那肯定不行。为false
当n不为0,m为0的时候,有东西和一个容量为0的背包,不用填东西就能“装满”,这行。
那么对于一个物品来说,它也只有三种情况,放得进去(放或者不放)或者不放
【!!!!!!】我们的代码是这样的:这里要特别特别!!!!!!!!!!!注意
dp的初始数组设置!!!!我之前设置为dp[n][m],那是不对的!!!应该是dp[n+1][m+1]为什么?因为确实有物品为0,空背包的情况存在,n的取值范围是0-N,m同理,都是双闭合空间!。

class Solution {
    public boolean canPartition(int[] nums) {
        //边界条件,如果和是奇数就没必要在这判断了。
        int sum = 0;
        for(int num:nums){
            sum+=num;
        }
        if(sum%2==1){
            return false;
        }
        //不是奇数,开启背包
        int half = sum/2;
        boolean[][] dp = new boolean[nums.length+1][half+1];
        //处理base case
        for(boolean[] row:dp){
            Arrays.fill(row,false);
        }
        for(int i=0;i<nums.length;i++){
            dp[i][0] = true;
        }
        //处理背包
        for(int i=1;i<=nums.length;i++){
            for(int w=1;w<=half;w++){
                if(nums[i-1]>w){//物品放不进背包
                    dp[i][w] = dp[i-1][w];
                }else{                 
                    dp[i][w] =
                        (dp[i-1][w-nums[i-1]]) || //加入背包,能不能完全放满的情况和dp[i-1][w-nums[i]]是一样的
                        (dp[i-1][w]);//不加入背包,可能相同重量i-1个物品就能放满 ;
                }
            }
        }
        return dp[nums.length][half];

    }
}

leercode518 零钱兑换2

这是个无限的背包问题,也就是说固定的背包,无限的物品
在这里插入图片描述

定义dp。y=dp[amount+1][coins.length+1].因为同时存在amount和coin均可以是0的情况呢。
然后这里有个奇葩的设定,当金额为0的时候,并不是没有方法可以凑出来,而是有一种方法可以“凑”出来,也就是不凑,所以会有dp[0][i]=1这种骚操作。然后推导公式的时候我推导错误了。。
我推导成了这个
dp[i][j] = dp[i-coins[j-1]][j] + dp[i][j-1]+1
因为我想着,这个数字放进去的话,那么就是凑成i-coins[j-1]这个金额再加一个金币即可,然后我就加一。。但是我忘记了
题目求得是组合数不是硬币数
首先组合数,比如我有1,2 两个硬币,每种硬币无限个。比如说
【1】组成2的方法有两种![2] 以及[1,1]
【2】那么组成3的方法呢?如果我加一就是三种,但是事实上,只是
[2] 和[1,1] 变成了 [2,1],[1,1,1],大家多加了一枚1块钱的硬币所以组合数不变,硬币的数量变了,正确的组合数应该是
dp[i][j] = dp[i-coins[j-1]][j] + dp[i][j-1]

class Solution {
    public int change(int amount, int[] coins) {
        //y=dp[amount+1][coins.length+1]
        int[][] dp = new int[amount+1][coins.length+1]; //金额为amount,硬币组合为0--coins.length情况下可以凑成金额为amount的组合树
        //basecase,金额是0,[]没有方式可以凑,一种方式可以凑那就是都不放。。没有硬币没有方法可以凑
        for(int i=0;i<dp[0].length;i++){
            dp[0][i] = 1;
        }
        for(int i=1;i<=amount;i++){
            for(int j=1;j<=coins.length;j++){
                if(coins[j-1]>i){
                    dp[i][j] = dp[i][j-1];//这枚硬币放不进去
                }else{
                    //dp[i][j] = dp[i-coins[j-1]][j] + dp[i][j-1]+1 ;//可以放进去+可以不放进去,错误的推导公式
                    dp[i][j] = dp[i-coins[j-1]][j] + dp[i][j-1];
                }
            }
        }
        return dp[amount][coins.length]; 
    }
}

子序列

大熊的子序列笔记

剪绳子

大熊的剪绳子笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值