518题是背包问题的变体,也称完全背包问题。

解法参考了该篇文章,然后对自己困惑的地方进行记录。

下面是该题的描述:



有一个背包,最大容量为 amount,有一系列物品 coins,每个物品的重量为 coins[i],每个物品的数量无限。请问有多少种方法,能够把背包恰好装满?求出所有的装满背包的方法数。

这里和一般的背包问题的不一样在于,物品的数量是无限的,这样的问题就称为完全背包问题。因为对于一般的0-1背包问题,物体的数量是1,要么选择该物品装入背包,要么不选择该物品装入背包。

  1. 首先思考问题的 状态选择

    状态有两个,就是「背包的容量」和「可选择的物品」,选择就是「装进背包」或者「不装进背包」 嘛,背包问题的套路都是这样。

  2. 第二步要明确 dp 数组的定义。

    首先看看刚才找到的「状态」,有两个,也就是说我们需要一个二维 dp 数组。dp[i][j] 的定义如下:

    若只使用前 i 个物品,当背包容量为 j 时,有 dp[i][j] 种方法可以装满背包。换句话说,翻译回我们题目的意思就是:

    若只使用 coins 中的前 i 个硬币的面值,若想凑出金额 j,有 dp[i][j] 种凑法。

    base case 为 dp[0][…] = 0, dp[…][0] = 1。因为如果不使用任何硬币面值,就无法凑出任何金额;如果凑出的目标金额为 0,那么“无为而治”就是唯一的一种凑法。

  3. 第三步,根据「选择」,思考状态转移的逻辑。 这一步是很重要的,做的时候就是没有准确写出状态转移条件。

    注意,我们这个问题的特殊点在于物品的数量是无限的,

    如果你不把这第 i 个物品装入背包,也就是说你不使用 coins[i] 这个面值的硬币,那么凑出面额 j 的方法数 dp[i][j] 应该等于 dp[i-1][j],继承之前的结果。

    如果你把这第 i 个物品装入了背包,也就是说你使用 coins[i] 这个面值的硬币,那么 dp[i][j] 应该等于 dp[i][j-coins[i-1]]。

    首先由于 i 是从 1 开始的,所以 coins 的索引是 i-1 时表示第 i 个硬币的面值。

    dp[i][j-coins[i-1]] 也不难理解,如果你决定使用这个面值的硬币,那么就应该关注如何凑出金额 j - coins[i-1]。

    这里的当选择第i个物体装入背包时,那么需要考虑题目给出的条件,在文章中已经强调了好几次,这里的数量是无限的,那么当说明第i个物体可以装很多次到背包中,所以当选择第i个物品时,

    dp[i][j]=dp[i-1][j]+dp[i][j-coins[i-1]],因为i是必选的,但是还可以继续选择是否装入背包

    总的方法数应该是这两者之和,(选和不选两种情况的总和)

  4. 实现代码如下:

    public int change(int amount, int[] coins) {
    
        int[][] dp = new int[coins.length + 1][amount + 1];
    for (int i = 0; i <= coins.length; i++) {
    dp[i][0] = 1;
    }
    for (int i = 1; i <= coins.length; i++) {
    for (int j = 0; j <= amount; j++) {
    if (coins[i - 1] - j > 0) {
    dp[i][j] = dp[i - 1][j];
    } else {
    dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
    }
    } }
    return dp[coins.length][amount];
    }
  5. 然后进行代码的优化压缩,因为dp[i][j]只依赖dp[i-1][…]和dp[i][…]

    public int change(int amount, int[] coins) {
    int[] dp = new int[amount + 1];
    dp[0] = 1;
    int n = coins.length;
    for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= amount; j++) {
    if (j - coins[i-1]>=0) {
    dp[j] = dp[j] + dp[j - coins[i - 1]];
    }
    }
    }
    return dp[amount];
    }

    if (j - coins[i-1]>=0) {

    dp[j] = dp[j] + dp[j - coins[i - 1]];

    }

    因为当求dp[j]的时候,此时dp[j - coins[i - 1]已经求出来了,相当于dp[i][j-coins[i-1]],而此时的dp[i][j] 的值相当于上一轮的外层循环存储的值,即dp[i-1][j]的值,那么正好可以这样进行压缩。

  6. 时间复杂度和空间复杂度

    这样进行压缩之后,时间复杂度为O(n*amount),空间复杂度为O(amount).

最新文章

  1. JQuery图片切换动画效果
  2. C++_系列自学课程_第_6_课_bitset集_《C++ Primer 第四版》
  3. 寒假学习计划(c++作业2)
  4. Android 中 Internal Storage 和 External Storage 的区别
  5. jquery mobile的事件
  6. struts(二) ---中参数传值
  7. Linux nginx日志按天分割实例
  8. 关于一些学习html和css的笔记
  9. JQuery学习(选择器-可见性-hidden)
  10. android 之 adb 启动问题的一般解决办法
  11. 边工作边刷题:70天一遍leetcode: day 86
  12. IE中出现 &quot;Stack overflow at line&quot; 错误的解决方法
  13. memcached+php客户端
  14. 关于js中for in的缺陷浅析
  15. vue Echarts 柱状图点击事件
  16. Java基础:内存模型
  17. frp内网 穿透映射使内网svn可外网访问
  18. 【Linux基础】查看某一端口是否开放(1025为例)
  19. sudo rm -rf iTunes.app Operation not permitted
  20. sql的一个查询,情景:a表中存在的数据,且在b表中不存在 (not in,not exists

热门文章

  1. LeetCode 64最小路径和
  2. mysql基础测试题
  3. Mybatis-03-日志
  4. 【算法•日更•第五十期】二分图(km算法)
  5. 【特别篇】不为人知的U盘秘密
  6. Java引用类型之软引用(2)
  7. Java mysql数据库连接Demo1
  8. Java 添加条码、二维码到PDF文档
  9. 微信小程序发送订阅消息(之前是模板消息)
  10. mac安装conda后,终端的用户名前面有一个(base),最佳解决方案