动态规划概念、01背包、完全背包

本文探讨动态规划在投资分配和背包问题中的应用,重点关注0/1背包和完全背包问题。通过实例解释了0/1背包问题的优化方法,包括采用逆序遍历的原因,并提供了代码示例。同时,介绍了完全背包的朴素方法及其优化策略,如在力扣322题中应用完全背包思想。
摘要由CSDN通过智能技术生成

在这里插入图片描述

投资分配问题

(股票投资问题)
在这里插入图片描述

背包问题(工厂排放问题、装载问题)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

1、0/1背包

下面参考视频:https://www.bilibili.com/video/BV1K4411X766
看完壁咚

博客参考:https://blog.csdn.net/qq_37767455/article/details/99086678
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
0/1背包问题代码(就是上面的放和不放):

 // W 为背包总体积
    // N 为物品数量
    // weights 数组存储 N 个物品的重量
    // values 数组存储 N 个物品的价值
    public int knapsack(int W, int N, int[] weights, int[] values) {
        int dp[][]=new int[N+1][W+1]; //dp[i][j]代表放入前i个,在j重量下最大价值
        for (int i = 1; i <=N; i++) {
            for (int j = 1; j <=W; j++) {
                if (j<weights[i-1]){
                    dp[i][j]=dp[i-1][j];
                }else {
                    dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weights[i-1]]+values[i-1]);
                }
            }
        }
        return dp[N][W];
    }

对其进行优化,使他变为一维dp,但是此时要逆序,因为如果是正序的话,dp[j]=Math.max(dp[j],dp[j-w[i]+v[i]])每次使用的是旁边的值(dp[j]) 【上一行的数据会被覆盖掉】,而不是上一行旁边的值。还是不懂可以试试这个:

正序会使用同一行数据,而不是上一行。

滚动数组解决逆序

这里滚动只保留最后一行而已
在这里插入图片描述
优化的01背包(重点,理解完全背包的重点):

// W 为背包总体积
    // N 为物品数量
    // weights 数组存储 N 个物品的重量
    // values 数组存储 N 个物品的价值
    public int knapsack(int W, int N, int[] weights, int[] values) {
        int dp[]=new int[W+1]; 
        for (int i = 1; i <=N; i++) {
            for (int j = W; j >=1; j--) {
                if (j>=weights[i-1]){
                    //这里第二个dp[j]是上一行的(类似之前的dp[i-1][j]),赋值完dp[j]才为当前行
                    //反正它就是只保留最新一行,那么最大就在最后面的那个元素。
                    dp[j]=Math.max(dp[j],dp[j-weights[i-1]]+values[i-1]);
                }
            }
        }
        return dp[W];
    }

上述正序试错:
在这里插入图片描述我好像懂了为什么需要逆序了:因为类似dp[i]=dp[i-1]+…,因为如果我们从前往后的话,dp【i-1】就不是上一行的了

1、(想了半天终于明白了为什么一维动态规划要逆序了。。。我也不知道自己说的好不好对不对。 dp[j] = dp[j] | dp[j - nums[i]] 实际上是用的 i - 1 层 dp[j] 和 dp[j - nums[i]] 得出的。 因此,如果顺序遍历的话, dp[j - num[i]] 会首先被更新成新的值, 然后再算 dp[j] = dp[j] | dp[j - nums[i]] 就不对了, 所以要逆序遍历。 emm 也不知道 讲的对不对。)

2、压缩到一维时,要采用逆序。dp[j] = dp[j] || dp[j - nums[i]] 可以理解为 dp[j] (新)= dp[j] (旧) || dp[j - nums[i]] (旧),如果采用正序的话 dp[j - nums[i]]会被之前的操作更新为新值

真的懂了:因为i每加1代表新的一行开始,由于dp[j-num[i]]每次都得使用的是上一行的数据。但是如果你正序的话,那么你在计算dp[j]的时候用到的dp[j-num[i]]是本行的,而不是上一行的,所以用逆序,逆序用到的dp[j-num[i]]是上一行的。[dp[j-w[i-1]也要是上一行的才行]]

2、完全背包

完全背包朴素方法,在0/1背包问题的优化版再添加多一个for循环,j/w[i]:代表的是当前容量下最多能放下第i件的最大数量:
在这里插入图片描述
但时间复杂度太高,我们使用下面优化版的:
在这里插入图片描述完全背包如果不拿那么值就从上一行来,如果拿了是从本行来的
代码与01背包优化版的差不多,只不过从正序而已:

// W 为背包总体积
    // N 为物品数量
    // weights 数组存储 N 个物品的重量
    // values 数组存储 N 个物品的价值
    public int knapsack(int W, int N, int[] weights, int[] values) {
        int dp[]=new int[W+1];
        for (int i = 1; i <=N; i++) {
            for (int j = 1; j <=W; j--) {
                if (j>=weights[i-1]){
                    //这里用正序的,数据从本行来的
                    dp[j]=Math.max(dp[j],dp[j-weights[i-1]]+values[i-1]);
                }
            }
        }
        return dp[W];
    }

力扣凑硬币322题,就是用了完全背包的思想

//类比完全背包可解,只不过它要求求出所以return,而且又点条件,所以fill,而且最小所以得min
    // W 为背包总体积 金额
    // N 为物品数量 coints[].length
    // weights 数组存储 N 个物品的重量 每个硬币大小
    // values 数组存储 N 个物品的价值 都为1
    public static int coinChange(int[] coins, int amount) {// [1,2,5]  11
        int max = amount + 1;	//max = 12,判断用的。共有amount个硬币,共有amount+1个状态,amount+1个金额,max也是为最后判断是否可以兑换立个条件,因为fill每个dp[]为max那么它就一定大于amount了,就返回-1
        int[] dp = new int[amount + 1]; // dp长度12
        Arrays.fill(dp, max);	// 填充12 //为了找不出就为最大的  必须将所有的dp赋最大值,因为要找最小值
        dp[0] = 0;			// 0 无法兑换出,有用的不能设置为12,比如1块在容量为1的话为dp[1-w[i]]=dp[0]+1=1不可能等于13
        //这里dp【i】的含义是在当前金额下,能够凑出来,硬币数(价值)最小
        for (int i = 0; i < coins.length; i++) {
            for (int j = 0; j <= amount; j++) {
                if (j >= coins[i]) {
                    dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];// -1时无法兑换,可以用[3,5] 7来验证
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值