动态规划题型——01背包

总结:

1.在做题的时候,考虑是否是01背包的题型:01背包定义是选或者不选一些题目看似表面跟01背包没有关系,但是经过题目剖析,列出一系列等式关系,进行转化,就会变成01背包。           【力扣494. 目标和、并且01背包的背包容量一定小于题目给出的nums.length,因为01背包是选或者不选,所以背包的容量一定是很紧缺的。

2.利用滚动数组进行压缩,当只有一个背包容量时:就使用一维数组(那么for循环就是先遍历物品,再遍历背包内容 ),有两个背包的容量时,就使用二维数组(那么for循环就是只遍历2个背包的容量,遍历背包的容量一定是倒序遍历

3.进行滚动数组:0/1背包:外循环物品(nums)、内循环背包容量(target)、遍历背包容量时要倒序并且背包容量要大于物品重量(target>=nums[i]);

4..递归公式模板

①最值问题:

dp[j] = max/min( dp[j], dp[ j - nums[i] ] + 1)

dp[j] = max/min( dp[j], dp[ j - nums[i] ] + nums[i] );

②存在问题(boolean):

dp[j] = dp[j] || dp[ j- nums[i] ];

③组合问题:

dp[j] += dp[ j-nums[i] ];

5.利用滚动数组,一定要记得初始化dp[0]!!! ——通常dp[0]=1 /dp[0]=true

求背包问题的最小值一般初始化dp[0]=0 且Arrays.fill(dp,Integer.MAX_VALUE)


内容知识来源

代码随想录 (programmercarl.com)

一篇文章吃透背包问题!(细致引入+解题模板+例题分析+代码呈现) - 分割等和子集 - 力扣(LeetCode) (leetcode-cn.com)


01 背包定义——选或者不选

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大


法一:使用二维数组

1.定义dp数组dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

//定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
int wlen = weight.length;//物品个数
int bagsize=4;//背包容量
int[][] dp = new int[wlen + 1][bagsize + 1];

2.确定递推公式:

再回顾一下dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

那么可以有两个方向推出来dp[i][j]

  • 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]=dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)注意这里的区间是0-i之间的物品,不放i,但是i-1放了,所以是dp[i-1][j]
  • 放物品i:当i放进去时,那么这时候整个物品集就被分成两部分,1到i-1和第i个,而这是i是确定要放进去的,那么就把j空间里的weight[i]给占据了,只剩下j-weight[i]的空间给前面i-1,那么只要这时候前面i-1在j-weight[i]空间里构造出最大价值,即dp[i - 1][j - weight[i]],再加上此时放入的i的价值value[i],就是dp[i][j]了-----放了物品i:那么他的价值为:value[i]+剩余物品放在容量为j-weight[i]的背包里 

所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

3.dp数组初始化——即初始化第一行和第一列

第一列初始化为0即:容量为0的时候,任何物品都装不了,故最大价值为0。【在定义二维数组的时候,就已经初始化了】

第一行初始化:因为物品0的重量为1,所以在背包容量<1的,最大价值都是0,背包容量>物品0的重量,初始化为物品0的价值。

 完整代码如下:

 public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagsize = 4;
        testweightbagproblem(weight, value, bagsize);
    }

    public static void testweightbagproblem(int[] weight, int[] value, int bagsize){
        int wlen = weight.length, value0 = 0;
        //定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
        int[][] dp = new int[wlen + 1][bagsize + 1];

        //初始化行:背包容量>物品0的重量,初始化为物品0的价值。
        for (int j =weight[0] ;  j<= wlen; i++){
            dp[0][j] = value[0];
        }

        //遍历顺序:先遍历物品,再遍历背包容量  i表示物品,j表示背包容量
        for (int i = 1; i <= wlen; i++){
            for (int j = 1; j <= bagsize; j++){
                //如果当前的背包容量<物品的重量——>那就是不取物品
                if (j < weight[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
                }
            }
        }

        //打印dp数组
        for (int i = 0; i <= wlen; i++){
            for (int j = 0; j <= bagsize; j++){
                System.out.print(dp[i][j] + " ");
            }
            System.out.print("\n");
        }
    }

法二:一维数组(滚动数组)——利用滚动数组进行遍历背包容量时:一定是倒序遍历

定义dp数组:因为我们将二维数组进行压缩至一维数组,所以我们只要定义一维数组的背包的容量就行。即定义一维的数组的长度就是背包的容量+1

//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];

递推公式: 

  • 不放物品i:因为重量没变化,所以没放,就表示不覆盖原来的数据即dp[j]=dp[j]
  • 放物品idp[j]=dp[j - weight[i]] + value[i]
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);

针对遍历顺序:一定是先遍历物品,再遍历背包容量。并且遍历物品是正序遍历,遍历背包容量一定是倒序遍历 

一个背包的容量压缩至一维数组:那么for循环就是先遍历物品数量,再遍历背包容量

  for (int i = 0; i < wLen; i++){//i是取不到wlen的
     for (int j = bagWeight; j >= weight[i]; j--)

两个背包的容量就是压缩至二维数组:那么for循环就是只遍历2个背包的容量,遍历背包的容量一定是倒序遍历

public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagWight = 4;
        testWeightBagProblem(weight, value, bagWight);
    }

    public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
        int wLen = weight.length;
        //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
        int[] dp = new int[bagWeight + 1];

        //遍历顺序:先遍历物品,再遍历背包容量
        for (int i = 0; i < wLen; i++){
            for (int j = bagWeight; j >= weight[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        //打印dp数组
        for (int j = 0; j <= bagWeight; j++){
            System.out.print(dp[j] + " ");
        }
    }

动态规划是一种将大问题分解为小问题进行解决的方法,背包问题是动态规划中最经典的题型之一。背包问题分为三类:01背包、完全背包和多重背包。其中,01背包问题是最经典的背包问题,也是动态规划的入门级必学算法。\[1\] 动态规划解决背包问题的核心思想是将问题分解为若干个小问题,先求解子问题,然后从子问题的解得到原问题的解。与分治法不同的是,动态规划适用于有重叠子问题的情况,即下一阶段的求解是建立在上一阶段的解的基础上进行进一步求解。通过填表的方式,逐步推进,最终得到最优解。\[2\] 多重背包问题介于01背包和完全背包之间,可以将其转化为01背包或完全背包问题来求解。对于某种物品,如果其数量乘以单位体积大于背包总容量,那么该物品与背包之间是完全背包问题。而对于某种物品,可以将其数量视为不同的物品,然后按照01背包问题进行处理。这样的转化可以在数据范围较小时适用,但在数量较大时可能会导致超时。因此,可以采用更精炼的划分方案,如二进制拆分,来减少物品分类的组数,从而优化算法的效率。\[3\] 总结来说,动态规划是一种解决背包问题的有效方法,通过将大问题分解为小问题,并利用子问题的解来求解原问题,可以得到背包的最优解。 #### 引用[.reference_title] - *1* *3* [【算法与数据结构】—— 动态规划背包问题](https://blog.csdn.net/the_ZED/article/details/104882665)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [动态规划算法解决经典背包问题](https://blog.csdn.net/m0_52110974/article/details/120122061)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值