• 代码随想录算法训练营第44天 | ● 完全背包● 518. 零钱兑换 II ● 377. 组合总和 Ⅳ


    文章目录


    前言

    完全背包;


    一、完全背包

    前情回顾,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。

    对于01背包的一维数组:

    首先,其背包的遍历顺序是倒序,因为01背包只允许取一个,倘若是正序,对于dp[j] = dp[j-weigh[i]) + value[i] ,会重复取多次,不符合01背包的定义;因而01背包的两层for循环,先循环物品,后循环背包,因为这样只会取一次(横向遍历);而先循环背包,后循环物品(纵向遍历),那么至多只能放一种,举个例子

    公式:dp[j] = dp[j-weigh[i]) + value[i]  + 背包是倒序

    1.先物品后背包: (横向)在i = 1,也就是第二行时,dp[4] = dp[4-3]+20 = dp[1]+20,在i=0时,dp[1] = 15;因而dp[4] = 15+20 = 35;

    2.先背包后物品:(纵向)因为是倒序遍历背包,在最后一列,当i=1,第二行时,dp[4] = dp[4-3]+20 = dp[1]+20,但因为是纵向,dp[1]还没有被赋值,仍是0,所以dp[4] = 0+20=20;

    完全背包和01背包问题唯一不同的地方就是,每种物品有无限件对于纯完全背包问题,其for循环的先后循环是可以颠倒的!

    因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。

    代码:(先物品)

    1. for (int i = 0; i < weight.length; i++){ // 遍历物品
    2. for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
    3. dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
    4. }
    5. }

    代码:(先背包,注意这里已经是正序,且代码相对逆序有所变动)

    1. for (int j = 1; j <= bagWeight; j++){ // 遍历背包容量
    2. for (int i = 0; i < weight.length; i++){ // 遍历物品
    3. if (j - weight[i] >= 0){
    4. dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
    5. }
    6. }
    7. }

    二、518. 零钱兑换 II

    递推公式:

    dp[j],j 为5,

    • 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
    • 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
    • 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
    • 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
    • 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包

    那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来

    初始化:

    首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]。dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。

    组合不强调元素之间的顺序,排列强调元素之间的顺序

    遍历顺序:

    本题要求凑成总和的组合数,元素之间明确要求没有顺序。所以纯完全背包是能凑成总和就行,不用管怎么凑的。本题是求凑出来的方案个数,且每个方案个数是为组合数。那么本题,两个for循环的先后顺序可就有说法了。

    如果求组合数就是外层for循环遍历物品,内层for遍历背包

    假设:coins[0] = 1,coins[1] = 5。

    那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

    所以这种遍历顺序中dp[j]里计算的是组合数!

    如果求排列数就是外层for遍历背包,内层for循环遍历物品

    背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。

    此时dp[j]里算出来的就是排列数!

    代码:

    1. class Solution {
    2. public int change(int amount, int[] coins) {
    3. //递推表达式
    4. int[] dp = new int[amount + 1];
    5. //初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装
    6. dp[0] = 1;
    7. for (int i = 0; i < coins.length; i++) {
    8. for (int j = coins[i]; j <= amount; j++) {
    9. dp[j] += dp[j - coins[i]];
    10. }
    11. }
    12. return dp[amount];
    13. }
    14. }

     

    三、377. 组合总和 Ⅳ 

    推导基本上与上一题没有什么区分,不同的是,零钱是组合,不强调顺序;本题是排列,强调顺序;因此 区别在于本题先遍历背包后遍历物品,这样使得顺序出现区别。

    1. class Solution {
    2. public int combinationSum4(int[] nums, int target) {
    3. int[] dp = new int[target + 1];
    4. dp[0] = 1;
    5. for (int i = 0; i <= target; i++) {
    6. for (int j = 0; j < nums.length; j++) {
    7. if (i >= nums[j]) {
    8. dp[i] += dp[i - nums[j]];
    9. }
    10. }
    11. }
    12. return dp[target];
    13. }
    14. }

     

    四、爬楼梯(进阶版)

    一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?

    1阶,2阶,.... m阶就是物品,楼顶就是背包。

    每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。

    问跳到楼顶有几种方法其实就是问装满背包有几种方法。

    此时大家应该发现这就是一个完全背包问题了!

    本题看起来是一道简单题目,稍稍进阶一下其实就是一个完全背包!

    如果我来面试的话,我就会先给候选人出一个 本题原题,看其表现,如果顺利写出来,进而在要求每次可以爬[1 - m]个台阶应该怎么写。

    顺便再考察一下两个for循环的嵌套顺序,为什么target放外面,nums放里面。

    这就能考察对背包问题本质的掌握程度,候选人是不是刷题背公式,一眼就看出来了。

    这么一连套下来,如果候选人都能答出来,相信任何一位面试官都是非常满意的。

    本题代码不长,题目也很普通,但稍稍一进阶就可以考察完全背包,而且题目进阶的内容在leetcode上并没有原题,一定程度上就可以排除掉刷题党了,简直是面试题目的绝佳选择!

    1. class Solution {
    2. public int climbStairs(int n) {
    3. int[] dp = new int[n + 1];
    4. int m = 2; //有兩個物品:itme1重量爲一,item2重量爲二
    5. dp[0] = 1;
    6. for (int i = 1; i <= n; i++) { // 遍历背包
    7. for (int j = 1; j <= m; j++) { //遍历物品
    8. if (i >= j) //當前的背包容量 大於 物品重量的時候,我們才需要記錄當前的這個裝得方法(方法數+)
    9. dp[i] += dp[i - j];
    10. }
    11. }
    12. return dp[n];
    13. }
    14. }

     


    总结

    完全背包;

  • 相关阅读:
    milvus 结合Thowee 文本转向量 ,新建表,存储,搜索,删除
    深入理解闭包:原理、应用与最佳实践
    光伏PID效应危害及修复方法之一
    win11右键显示更多选项关闭的四种方法
    RocketMQ源码分析:Consumer消费偏移量
    微信开发者工具下载
    Redis追本溯源(一)先导:关于Redis的三个问题
    LeetCode216. Combination Sum III
    JMETER自适应高分辨率的显示器
    【14】c++11新特性 —>共享智能指针
  • 原文地址:https://blog.csdn.net/m0_51671538/article/details/132730293