你的背包,背到现在还没烂

245 阅读3分钟

1、01背包

题目

N件物品和一个容量为V的背包。第i件物品的体积是w[i],价值是c[i]。求解将哪些物品装入背包可使价值总和最大。

设dp[j]为当背包容量为j时,当前背包能达到的最大价值。

那么对于一个物品,如果它没有超重,我可以拿或者不拿,有以下两种情况:

  • 如果拿: (dp[j-w[i]] + c[i]) 当前的价值为dp[当前背包重量-当前物品重量] + 当前物品价值,也就是dp[j-w[i]] + c[i]

dp[j-w[i]]表示,如果我拿了这个物品,剩余容量能够达到的最大价值。

  • 不拿: dp[j] 如果不拿,就代表容量不变,价值也不变

对于这个物品,在拿和不拿的情况下,我需要达到最大价值,就需要取这两种情况的最大价值. 也就是

dp[j] = max(dp[j-w[i]] + c[i], dp[j])

那么对于这个问题,可以这样解决:

fun main() {
    // 物品的重量
    val ws = intArrayOf(9, 2, 1)
    // 物品的价值
    val cs = intArrayOf(100, 2, 3)
    // 背包的重量
    val v = 10
    // 定义dp数组,dp[j]为当背包容量为j时,当前背包能达到的最大价值。
    val dp = IntArray(v + 1)
    // 遍历我的物品
    for (i in 0..(ws.size - 1)) {
        // 从容量逆序到当前物品重量, 逆序是因为dp[j] 需要用到dp[j - ws[i]], j - ws[i]肯定比j小
        // 所以为了避免覆盖dp[j - ws[i]]的值, 需要先逆序先算大的
        for (j in v downTo ws[i]) {
            // 拿和不拿取大
            dp[j] = max(dp[j - ws[i]] + cs[i], dp[j])
        }
    }
    // 输出容量为v时能达到的最大价值
    println(dp[v])
}

2、零钱兑换

题目

来自322. 零钱兑换 - 力扣

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

使用一个dp数组, dp[j]表示当金额为i时,能够使用的最少硬币.

dp[0]的初始值为0,因为0无法用任何硬币凑成,方案数量为0.

对于一个金额j,我们可以用 j 减去当前硬币的面值的最小数量加上当前面值的一枚硬币, 也就是dp[j - coin] + 1, 遍历所有面值的硬币,取其最小值就是当前金额所需数量的最小值

dp[j] = min(dp[j-coin1] + 1, dp[j-coin2] + 1, dp[j-coin3] + 1, .........)

最终代码如下:


fun main() {
    // 硬币
    val coins = intArrayOf(1, 3, 4)
    val amount = 7
    val dp = IntArray(amount + 1) { amount + 1 }
    // 凑齐0元有0种方法
    dp[0] = 0
    // 硬币是无限的,所以先遍历金额
    for (i in 1..amount) {
        // 遍历硬币
        for (coin in coins) {
            // 硬币的面额必须小于等于金额
            if (coin <= i) {
                // 对于一个面额,我可以用 (i 减去当前面额的面值)所需要的最小数量 + 1(当前面额) 和当前面额所需硬币最小数量 对比
                dp[i] = min(dp[i - coin] + 1, dp[i])
            }
        }
    }
    // 如果最小数量为amount + 1,表示凑不到, 返回-1
    println(if (dp[amount] == amount + 1) -1 else dp[amount])
}

零钱兑换Ⅱ

题目

来自518. 零钱兑换 II - 力扣(LeetCode)

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

dp[j] 表示凑齐j元的方案. 凑齐j元的方案可以加上(凑齐j - 硬币面额的方案), 遍历所有面额的硬币,一直累加.

硬币放在外层循环,保证了凑齐的方案不会重复: 比如先用一元面额的硬币凑:

dp[1] += dp[0] // 1
dp[2] += dp[1] // 1 1
dp[3] += dp[2] // 1 1 1
.....

再用两元面额的硬币凑

dp[2] += dp[0] // 2
dp[3] += dp[1] // 1 2
dp[4] += dp[2] // 1 1 2

再用三元面额的硬币凑

dp[3] += dp[0] // 3
dp[4] += dp[1] // 1 3
dp[5] += dp[2] // 1 1 3 和 2 3

最终代码:


fun main() {
    // 硬币
    val coins = intArrayOf(1, 2, 3)
    val amount = 5
    val dp = IntArray(amount + 1)
    dp[0] = 1
    for (coin in coins) {
        for (i in 1..amount) {
            if (i >= coin) {
                dp[i] += dp[i - coin]
            }
        }
    }
    println(dp[amount])
}